Você é um engenheiro backend sênior. Sua tarefa é criar um backend Node.js robusto para integrar: * HubSpot (via webhook e API) * Plataforma intermediária (que envia portalId e dados do cliente) * Sistema interno de templates (multi-cliente) * Provedores de WhatsApp (ex: Infobip) --- # 🎯 OBJETIVO Criar um sistema capaz de: 1. Receber requisições do HubSpot (via webhook) 2. Identificar cliente via portalId 3. Gerenciar templates internamente (multi-cliente) 4. Sincronizar templates com HubSpot (dropdown property) 5. Criar automaticamente a property no HubSpot se não existir 6. Enviar mensagens via provider (ex: Infobip) 7. Garantir isolamento total entre clientes 8. Estrutura pronta para multi-WABA e multi-provider --- # 🏗️ STACK * Node.js * Express * Axios * Redis (ou memória simulada) --- # 📦 ESTRUTURA DE PASTAS ```bash src/ controllers/ services/ providers/ repositories/ routes/ jobs/ utils/ ``` --- # 🧩 MODELAGEM DE DADOS ## CLIENT ```json { "portalId": "123456", "clientId": "cliente_1", "wabaId": "waba_1", "provider": "infobip", "accessToken": "hubspot_token" } ``` --- ## TEMPLATE ```json { "id": "tpl_123", "name": "Boas Vindas", "category": "MARKETING", "language": "pt_BR", "status": "APPROVED", "provider": "infobip", "providerTemplateId": "abc123", "wabaId": "waba_1" } ``` --- ## SENDER ```json { "id": "sender_1", "wabaId": "waba_1", "number": "551199999999", "status": "ACTIVE" } ``` --- # 🧠 FUNCIONALIDADES PRINCIPAIS --- ## 1. WEBHOOK HUBSPOT ### Endpoint: ```http POST /webhook/hubspot ``` ### Payload esperado: ```json { "portalId": "123456", "onsms_template": "tpl_123", "phone": "+558399999999" } ``` --- ## Lógica: ```js async function handleWebhook(req, res) { const { portalId, onsms_template, phone } = req.body const client = await getClientByPortalId(portalId) if (!client) { return res.status(404).json({ error: "Client not found" }) } const template = await getTemplateByIdAndWaba( onsms_template, client.wabaId ) if (!template || template.status !== "APPROVED") { return res.status(400).json({ error: "Invalid template" }) } const sender = await selectSender(client.wabaId) try { await sendViaProvider({ provider: client.provider, from: sender.number, to: phone, templateId: template.providerTemplateId }) } catch (err) { console.error("Send failed:", err) return res.status(500).json({ error: "Send failed" }) } return res.json({ success: true }) } ``` --- ## 2. GARANTIR PROPERTY NO HUBSPOT ```js async function ensureHubspotProperty(client) { try { await axios.get( `https://api.hubapi.com/crm/v3/properties/contacts/onsms_template`, { headers: { Authorization: `Bearer ${client.accessToken}` } } ) } catch (err) { await axios.post( `https://api.hubapi.com/crm/v3/properties/contacts`, { name: "onsms_template", label: "Template WhatsApp", type: "enumeration", fieldType: "select", options: [] }, { headers: { Authorization: `Bearer ${client.accessToken}` } } ) } } ``` --- ## 3. SYNC DE TEMPLATES → HUBSPOT ```js async function syncTemplatesToHubspot(client) { await ensureHubspotProperty(client) const templates = await getTemplatesByWaba(client.wabaId) const approvedTemplates = templates.filter(t => t.status === "APPROVED") const options = approvedTemplates.map(t => ({ label: `${t.name} | ${t.category} | ${t.language.toUpperCase()}`, value: t.id })) // IMPORTANT: HubSpot replaces all options, always send full list await axios.patch( `https://api.hubapi.com/crm/v3/properties/contacts/onsms_template`, { options }, { headers: { Authorization: `Bearer ${client.accessToken}`, "Content-Type": "application/json" }, timeout: 5000 } ) } ``` --- ## 4. SELEÇÃO DE SENDER ```js function selectSender(wabaId) { const senders = getSendersByWaba(wabaId) const active = senders.filter(s => s.status === "ACTIVE") if (!active.length) throw new Error("No active sender") return active[0] } ``` --- ## 5. ENVIO VIA PROVIDER (INFOBIP) ```js async function sendViaProvider({ provider, from, to, templateId }) { if (provider === "infobip") { return axios.post( "https://xxxxx.api.infobip.com/whatsapp/1/message/template", { messages: [ { from, to, content: { templateName: templateId } } ] }, { headers: { Authorization: "App YOUR_API_KEY", "Content-Type": "application/json" }, timeout: 5000 } ) } } ``` --- ## 6. JOB DE SINCRONIZAÇÃO ```js async function syncAllClients() { const clients = await getAllClients() for (const client of clients) { await syncTemplatesToHubspot(client) } } ``` Executar: * a cada 5 minutos * ou manualmente --- # 🔌 INTEGRAÇÃO COM PLATAFORMA INTERMEDIÁRIA O sistema deve assumir que: * portalId já vem pronto * cliente já está autenticado * requisições chegam via webhook --- # ⚠️ REGRAS IMPORTANTES * Nunca misturar templates entre clientes * Sempre filtrar templates APPROVED * Nunca confiar em templateId sem validar com wabaId * Sempre mapear portalId → cliente * Sempre enviar lista completa de templates no sync --- # 🚀 RESULTADO ESPERADO Backend capaz de: * receber webhook do HubSpot * identificar cliente corretamente * buscar template interno com segurança * enviar via provider (Infobip) * sincronizar templates com HubSpot * criar property automaticamente * escalar para múltiplos clientes --- # 💡 DIFERENCIAL Preparar o sistema para: * múltiplos providers (infobip, twilio, etc) * múltiplos senders por cliente * uso como SaaS