Como Integrar Chatbot com ERP e CRM: Guia Tecnico
Tutorial para conectar seu chatbot com sistemas legados. APIs, webhooks, bancos de dados e solucoes quando nao ha API disponivel.
Chatbot que nao acessa seus sistemas e apenas um FAQ glorificado. Este guia mostra como conectar seu bot ao ERP, CRM e outros sistemas da empresa.
Tipos de Integracao
| Tipo | Quando Usar | Complexidade |
|---|---|---|
| API REST | Sistema tem API documentada | Baixa |
| Webhook | Sistema envia eventos | Baixa |
| Banco Direto | Sistema sem API | Media |
| RPA/Automacao | Sistema so tem interface | Alta |
| Arquivo/FTP | Sistemas muito antigos | Media |
1. Integracao via API REST
A forma mais limpa quando o sistema tem API.
Exemplo: Consultar Cliente no CRM
const axios = require('axios');
class CRMClient {
constructor(baseUrl, apiKey) {
this.client = axios.create({
baseURL: baseUrl,
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
}
async getCustomer(phone) {
try {
const response = await this.client.get('/customers', {
params: { phone: this.normalizePhone(phone) }
});
return response.data[0] || null;
} catch (error) {
console.error('Erro ao buscar cliente:', error.message);
return null;
}
}
async createCustomer(data) {
const response = await this.client.post('/customers', {
name: data.name,
phone: this.normalizePhone(data.phone),
email: data.email
});
return response.data;
}
async addInteraction(customerId, message) {
return this.client.post(`/customers/${customerId}/interactions`, {
type: 'whatsapp',
content: message,
timestamp: new Date().toISOString()
});
}
normalizePhone(phone) {
return phone.replace(/\D/g, '');
}
}
// Uso no chatbot
const crm = new CRMClient('https://api.seucrm.com', process.env.CRM_API_KEY);
async function handleMessage(from, message) {
// Buscar ou criar cliente
let customer = await crm.getCustomer(from);
if (!customer) {
customer = await crm.createCustomer({
phone: from,
name: 'WhatsApp Lead'
});
}
// Registrar interacao
await crm.addInteraction(customer.id, message);
// Continuar fluxo com dados do cliente
return processWithCustomerData(customer, message);
}
Exemplo: Consultar Estoque no ERP
class ERPClient {
constructor(config) {
this.baseUrl = config.baseUrl;
this.client = axios.create({
baseURL: config.baseUrl,
auth: {
username: config.user,
password: config.password
}
});
}
async getProductStock(sku) {
const response = await this.client.get(`/products/${sku}/stock`);
return {
available: response.data.quantity_available,
reserved: response.data.quantity_reserved,
warehouse: response.data.warehouse_id
};
}
async getProductPrice(sku, customerId = null) {
const params = customerId ? { customer_id: customerId } : {};
const response = await this.client.get(`/products/${sku}/price`, { params });
return {
price: response.data.unit_price,
discount: response.data.discount_percentage,
finalPrice: response.data.final_price
};
}
async createOrder(customerId, items) {
const response = await this.client.post('/orders', {
customer_id: customerId,
items: items.map(item => ({
sku: item.sku,
quantity: item.quantity
})),
source: 'whatsapp_bot'
});
return response.data;
}
}
// Uso
const erp = new ERPClient({
baseUrl: 'https://erp.empresa.com/api/v1',
user: process.env.ERP_USER,
password: process.env.ERP_PASS
});
async function checkAvailability(productCode) {
const stock = await erp.getProductStock(productCode);
if (stock.available > 0) {
return `Temos ${stock.available} unidades disponiveis!`;
} else {
return 'Produto sem estoque no momento.';
}
}
2. Integracao via Webhook
Sistema envia eventos para seu chatbot.
Recebendo Eventos do CRM
// Endpoint para receber webhooks do CRM
app.post('/webhooks/crm', async (req, res) => {
const { event, data } = req.body;
// Validar assinatura
if (!validateSignature(req)) {
return res.status(401).send('Invalid signature');
}
res.sendStatus(200); // Responder rapido
// Processar evento
switch (event) {
case 'deal.won':
await notifyCustomerDealWon(data.customer_phone, data.deal_name);
break;
case 'ticket.created':
await notifyCustomerTicketCreated(data.customer_phone, data.ticket_id);
break;
case 'invoice.overdue':
await remindCustomerPayment(data.customer_phone, data.invoice);
break;
}
});
async function notifyCustomerDealWon(phone, dealName) {
await sendWhatsAppMessage(phone,
`Parabens! Sua compra de ${dealName} foi confirmada. ` +
`Em breve voce recebera mais detalhes.`
);
}
Enviando Webhooks Para Outros Sistemas
async function notifyExternalSystems(event, data) {
const webhooks = [
'https://erp.empresa.com/webhooks/whatsapp',
'https://crm.empresa.com/webhooks/chat',
'https://analytics.empresa.com/events'
];
const payload = {
event: event,
data: data,
timestamp: new Date().toISOString(),
source: 'whatsapp_bot'
};
// Enviar para todos em paralelo
await Promise.allSettled(
webhooks.map(url =>
axios.post(url, payload, {
timeout: 5000,
headers: {
'X-Webhook-Secret': process.env.WEBHOOK_SECRET
}
})
)
);
}
// Uso
await notifyExternalSystems('message.received', {
from: customerPhone,
message: messageText,
intent: detectedIntent
});
3. Integracao Direta com Banco de Dados
Quando o sistema nao tem API, acesse o banco diretamente.
Cuidados
- Use usuario com permissoes READ-ONLY
- Nunca altere dados diretamente (use stored procedures)
- Considere criar views para abstrair complexidade
- Monitore impacto de performance
Exemplo: MySQL/MariaDB
const mysql = require('mysql2/promise');
class ERPDirect {
constructor(config) {
this.pool = mysql.createPool({
host: config.host,
user: config.user,
password: config.password,
database: config.database,
waitForConnections: true,
connectionLimit: 10
});
}
async getCustomerByPhone(phone) {
const [rows] = await this.pool.execute(
`SELECT
c.id,
c.nome,
c.email,
c.limite_credito,
COALESCE(SUM(p.valor_pendente), 0) as saldo_devedor
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id = c.id AND p.status = 'pendente'
WHERE c.telefone = ?
GROUP BY c.id`,
[phone]
);
return rows[0] || null;
}
async getLastOrders(customerId, limit = 5) {
const [rows] = await this.pool.execute(
`SELECT
p.id,
p.data_pedido,
p.valor_total,
p.status,
GROUP_CONCAT(i.nome_produto) as produtos
FROM pedidos p
JOIN itens_pedido i ON i.pedido_id = p.id
WHERE p.cliente_id = ?
GROUP BY p.id
ORDER BY p.data_pedido DESC
LIMIT ?`,
[customerId, limit]
);
return rows;
}
async checkProductAvailability(sku) {
const [rows] = await this.pool.execute(
`SELECT
p.nome,
e.quantidade_disponivel,
e.quantidade_reservada,
p.preco_venda
FROM produtos p
JOIN estoque e ON e.produto_id = p.id
WHERE p.sku = ?`,
[sku]
);
return rows[0] || null;
}
}
Exemplo: SQL Server
const sql = require('mssql');
class ERPSqlServer {
constructor(config) {
this.config = {
user: config.user,
password: config.password,
server: config.host,
database: config.database,
options: {
encrypt: true,
trustServerCertificate: true
}
};
this.pool = null;
}
async connect() {
this.pool = await sql.connect(this.config);
}
async getOpenOrders(customerId) {
const result = await this.pool.request()
.input('customerId', sql.Int, customerId)
.query(`
SELECT
NumeroPedido,
DataPedido,
ValorTotal,
StatusPedido
FROM Pedidos
WHERE ClienteId = @customerId
AND StatusPedido IN ('Aberto', 'EmProcessamento')
ORDER BY DataPedido DESC
`);
return result.recordset;
}
// Usar stored procedure para operacoes de escrita
async createServiceTicket(customerId, description) {
const result = await this.pool.request()
.input('customerId', sql.Int, customerId)
.input('description', sql.NVarChar(sql.MAX), description)
.input('source', sql.VarChar(50), 'WhatsApp')
.execute('sp_CriarChamado');
return result.recordset[0];
}
}
4. Integracao via Arquivos (FTP/SFTP)
Para sistemas que so exportam/importam arquivos.
Exemplo: Importar Lista de Precos
const Client = require('ssh2-sftp-client');
const csv = require('csv-parser');
class ERPFileIntegration {
constructor(config) {
this.sftpConfig = config;
this.priceCache = new Map();
}
async syncPrices() {
const sftp = new Client();
try {
await sftp.connect(this.sftpConfig);
// Baixar arquivo de precos
const remotePath = '/export/precos_vigentes.csv';
const localPath = '/tmp/precos.csv';
await sftp.get(remotePath, localPath);
await sftp.end();
// Processar CSV
return new Promise((resolve, reject) => {
const prices = [];
fs.createReadStream(localPath)
.pipe(csv())
.on('data', (row) => {
this.priceCache.set(row.sku, {
price: parseFloat(row.preco),
stock: parseInt(row.estoque),
updated: new Date()
});
prices.push(row);
})
.on('end', () => resolve(prices))
.on('error', reject);
});
} finally {
if (sftp.sftp) await sftp.end();
}
}
getPrice(sku) {
return this.priceCache.get(sku);
}
// Agendar sync periodico
startSync(intervalMs = 3600000) { // 1 hora
this.syncPrices(); // Sync inicial
setInterval(() => this.syncPrices(), intervalMs);
}
}
Exportar Pedidos Para ERP
async function exportOrdersToERP(orders) {
const sftp = new Client();
// Gerar arquivo CSV
const csvContent = orders.map(order =>
[
order.id,
order.customer_id,
order.items.map(i => `${i.sku}:${i.qty}`).join(';'),
order.total,
order.created_at
].join(',')
).join('\n');
const header = 'pedido_id,cliente_id,itens,valor_total,data\n';
const fileName = `pedidos_whatsapp_${Date.now()}.csv`;
fs.writeFileSync(`/tmp/${fileName}`, header + csvContent);
// Upload para ERP
await sftp.connect(sftpConfig);
await sftp.put(`/tmp/${fileName}`, `/import/${fileName}`);
await sftp.end();
return fileName;
}
5. Integracao via RPA (Automacao de Interface)
Ultimo recurso quando sistema so tem interface grafica.
Exemplo com Puppeteer
const puppeteer = require('puppeteer');
class ERPScraper {
constructor(config) {
this.config = config;
this.browser = null;
this.page = null;
}
async init() {
this.browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox']
});
this.page = await this.browser.newPage();
await this.login();
}
async login() {
await this.page.goto(this.config.loginUrl);
await this.page.type('#username', this.config.user);
await this.page.type('#password', this.config.password);
await this.page.click('#login-button');
await this.page.waitForNavigation();
}
async searchCustomer(phone) {
await this.page.goto(`${this.config.baseUrl}/clientes`);
// Preencher busca
await this.page.type('#search-phone', phone);
await this.page.click('#search-button');
await this.page.waitForSelector('.customer-result', { timeout: 5000 });
// Extrair dados
const customer = await this.page.evaluate(() => {
const row = document.querySelector('.customer-result');
if (!row) return null;
return {
name: row.querySelector('.customer-name').textContent,
email: row.querySelector('.customer-email').textContent,
balance: row.querySelector('.customer-balance').textContent
};
});
return customer;
}
async createTicket(customerId, description) {
await this.page.goto(`${this.config.baseUrl}/chamados/novo`);
await this.page.select('#customer-select', customerId);
await this.page.type('#description', description);
await this.page.click('#submit-ticket');
await this.page.waitForSelector('.ticket-created');
const ticketId = await this.page.$eval(
'.ticket-number',
el => el.textContent
);
return ticketId;
}
async close() {
if (this.browser) await this.browser.close();
}
}
Problemas do RPA:
- Fragil (quebra se interface muda)
- Lento
- Dificil de debugar
- Nao escala bem
Use apenas quando nao ha alternativa.
Padrao de Integracao Recomendado
Camada de Abstracao
// interfaces/erp.interface.js
class ERPInterface {
async getCustomer(phone) { throw new Error('Not implemented'); }
async getStock(sku) { throw new Error('Not implemented'); }
async createOrder(data) { throw new Error('Not implemented'); }
}
// adapters/totvs.adapter.js
class TotvsAdapter extends ERPInterface {
async getCustomer(phone) {
// Implementacao especifica TOTVS
}
}
// adapters/sap.adapter.js
class SAPAdapter extends ERPInterface {
async getCustomer(phone) {
// Implementacao especifica SAP
}
}
// factory
function createERPClient(type, config) {
switch (type) {
case 'totvs': return new TotvsAdapter(config);
case 'sap': return new SAPAdapter(config);
case 'bling': return new BlingAdapter(config);
default: throw new Error(`Unknown ERP: ${type}`);
}
}
// Uso no chatbot - nao sabe qual ERP e
const erp = createERPClient(process.env.ERP_TYPE, config);
const customer = await erp.getCustomer(phone);
Cache de Dados
const NodeCache = require('node-cache');
class CachedERPClient {
constructor(erpClient, ttlSeconds = 300) {
this.erp = erpClient;
this.cache = new NodeCache({ stdTTL: ttlSeconds });
}
async getCustomer(phone) {
const cacheKey = `customer:${phone}`;
let customer = this.cache.get(cacheKey);
if (!customer) {
customer = await this.erp.getCustomer(phone);
if (customer) {
this.cache.set(cacheKey, customer);
}
}
return customer;
}
async getStock(sku) {
const cacheKey = `stock:${sku}`;
let stock = this.cache.get(cacheKey);
if (!stock) {
stock = await this.erp.getStock(sku);
this.cache.set(cacheKey, stock, 60); // TTL menor para estoque
}
return stock;
}
invalidateCustomer(phone) {
this.cache.del(`customer:${phone}`);
}
}
Filas Para Operacoes de Escrita
const Queue = require('bull');
const orderQueue = new Queue('orders', 'redis://localhost:6379');
// Chatbot adiciona na fila
async function createOrderAsync(orderData) {
const job = await orderQueue.add(orderData, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 1000
}
});
return job.id;
}
// Worker processa
orderQueue.process(async (job) => {
const erp = createERPClient(process.env.ERP_TYPE, config);
const order = await erp.createOrder(job.data);
// Notificar cliente
await sendWhatsAppMessage(
job.data.customerPhone,
`Pedido #${order.id} criado com sucesso!`
);
return order;
});
// Tratar falhas
orderQueue.on('failed', async (job, err) => {
console.error(`Pedido falhou: ${err.message}`);
await sendWhatsAppMessage(
job.data.customerPhone,
'Houve um problema ao processar seu pedido. Nossa equipe vai entrar em contato.'
);
});
Monitoramento
const prometheus = require('prom-client');
// Metricas
const erpRequestDuration = new prometheus.Histogram({
name: 'erp_request_duration_seconds',
help: 'ERP request duration in seconds',
labelNames: ['method', 'status']
});
const erpRequestErrors = new prometheus.Counter({
name: 'erp_request_errors_total',
help: 'Total ERP request errors',
labelNames: ['method', 'error_type']
});
// Wrapper com metricas
async function withMetrics(method, fn) {
const end = erpRequestDuration.startTimer({ method });
try {
const result = await fn();
end({ status: 'success' });
return result;
} catch (error) {
end({ status: 'error' });
erpRequestErrors.inc({ method, error_type: error.code || 'unknown' });
throw error;
}
}
// Uso
async function getCustomer(phone) {
return withMetrics('getCustomer', () => erp.getCustomer(phone));
}
Checklist de Integracao
- Credenciais em variaveis de ambiente
- Rate limiting configurado
- Retry com backoff implementado
- Cache para dados que nao mudam frequentemente
- Fila para operacoes de escrita
- Logs estruturados
- Metricas de performance
- Alertas para falhas
- Documentacao da API/integracao
- Teste de integracao automatizado
Sakaguchi IA - Inteligencia Artificial para Empresas Brasileiras