Integracao com Salesforce/HubSpot — CRM externo
Padrao tecnico de integracao bidirecional com CRM externo (Salesforce, HubSpot, RD Station). Tokens OAuth, sincronizacao, conflitos.
SIGACRM perdeu mercado pra CRMs SaaS especializados (Salesforce, HubSpot, RD Station). Padrao moderno: Protheus eh o "back-office" (cadastros, faturamento), CRM eh o "front" (vendas, marketing). Eles precisam sincronizar.
Cenarios tipicos
- CRM cria oportunidade → Protheus cria pedido
- Protheus altera cliente (SA1) → CRM atualiza conta
- CRM cadastra lead novo → Protheus cria/atualiza SA1
- Protheus fatura (gera NF) → CRM marca oportunidade como ganha
Arquitetura
┌──────────────────┐ ┌─────────────────┐ ┌──────────────────┐
│ Salesforce/HS │ ←→ │ Middleware/ │ ←→ │ Protheus REST │
│ (CRM) │ │ Webhook handler │ │ API + PE │
└──────────────────┘ └─────────────────┘ └──────────────────┘
Codigo: REST endpoint pra receber webhook do CRM
#include "tlpp-core.th"
namespace custom.integ.crm
@Post("/crm/webhook/lead")
function NovoLeadCrm()
Local oBody := oRequest:getJsonBody()
Local cNome := oBody:getJsonObject("name")
Local cEmail := oBody:getJsonObject("email")
Local cCnpj := oBody:getJsonObject("document")
// Tenta achar cliente existente
SA1->(DBSetOrder(3)) // por CNPJ
If SA1->(DBSeek(xFilial("SA1") + cCnpj))
// Atualiza
RecLock("SA1", .F.)
SA1->A1_EMAIL := cEmail
SA1->(MsUnlock())
oResponse:setStatus(200)
oResponse:setBody('{"status":"updated","cod":"' + SA1->A1_COD + '"}')
Else
// Cria via MsExecAuto
local aRet := U_IncCliViaApi(cNome, cEmail, cCnpj)
oResponse:setStatus(201)
oResponse:setBody('{"status":"created","cod":"' + aRet[1] + '"}')
EndIf
Return
endfunction
Codigo: PE M030ALT envia pro CRM (saida)
User Function M030ALT()
// Envia atualizacao pro CRM em background
StartJob("U_EnviaCrm", GetEnvServer(), .F., SA1->A1_COD, SA1->A1_LOJA)
Return
User Function EnviaCrm(cCod, cLoja)
RpcSetType(3)
RpcSetEnv("99", "01", "admin", "senha")
Local oHttp := FwHttpClient():New()
oHttp:SetHeader("Authorization", "Bearer " + GetTokenCRM())
oHttp:SetHeader("Content-Type", "application/json")
SA1->(DBSetOrder(1))
SA1->(DBSeek(xFilial("SA1") + cCod + cLoja))
Local oPayload := JsonObject():New()
oPayload["external_id"] := cCod + cLoja
oPayload["name"] := AllTrim(SA1->A1_NOME)
oPayload["document"] := AllTrim(SA1->A1_CGC)
oPayload["email"] := AllTrim(SA1->A1_EMAIL)
oHttp:Post("https://api.salesforce.com/v2/accounts", oPayload:ToJson())
RpcClearEnv()
Return
OAuth/Token management
// Token tem expiracao — usar tabela Z pra cache
Static Function GetTokenCRM()
Z9T->(DBSetOrder(1))
If Z9T->(DBSeek("CRM" + "TOKEN"))
If Z9T->Z9T_EXPIRA > Now()
Return Z9T->Z9T_VALOR
EndIf
EndIf
// Renovar
Local oHttp := FwHttpClient():New()
oHttp:Post("https://api.salesforce.com/oauth/token", ;
"grant_type=client_credentials&client_id=...&client_secret=...")
Local oResp := JsonObject():New()
oResp:FromJson(oHttp:GetBody())
RecLock("Z9T", !Z9T->(Found()))
Z9T->Z9T_TIPO := "CRM"
Z9T->Z9T_CHAVE := "TOKEN"
Z9T->Z9T_VALOR := oResp["access_token"]
Z9T->Z9T_EXPIRA := Now() + oResp["expires_in"]/86400
Z9T->(MsUnlock())
Return oResp["access_token"]
Pegadinhas operacionais
- Conflitos: ambos sistemas alteram mesmo cliente ao mesmo tempo. Definir "fonte da verdade" por campo.
- Idempotencia: webhook do CRM pode chamar 2x em retry. Use external_id pra detectar duplicata.
- Rate limit: CRMs tem cota (ex: Salesforce 100k chamadas/dia). Acumule em batch.
- Schema drift: CRM muda layout API sem aviso. Versione e teste em integracao.
- Latencia: nao bloqueie usuario Protheus esperando CRM responder. Use jobs assincronos.
- Compliance: dados de cliente em terceiro = LGPD. Termo de uso e DPO ciente.
Cliente externo recomendado
- Salesforce: usar Bulk API 2.0 pra cargas grandes; REST API pra sync transacional.
- HubSpot: API CRM v3 ja moderna, JSON limpo, paginacao por cursor.
- RD Station: API publica, mas tem rate limit agressivo (60/min).
- TOTVS CRM SFA: SDK proprio, integracao mais natural se ja for ecossistema TOTVS.