Integre seu ERP com a Hapee
Os fluxos completos, o mapeamento de campos e o checklist de saída. Escrito para ser suficiente sem a gente ao seu lado.
Antes de escrever uma linha
Três coisas que definem todo o resto:
- A subconta vem da credencial. Nunca é enviada como parâmetro. Uma credencial pertence a uma subconta e só pode ler e escrever nela.
- Há duas formas de saber o que acontece e ambas entregam exatamente o mesmo: webhooks (nós avisamos) ou
GET /events(você consulta). Você escolhe conforme seu ERP possa ou não expor uma URL pública, não conforme o que quer receber. - Toda escrita aceita
Idempotency-Key. Uma nova tentativa por queda de rede não duplica nada. Use desde o primeiro dia, não quando o problema aparecer.
curl https://beta.hapee.ai/api/v1/me \
-H "Authorization: Bearer hp_test_SUA_CREDENCIAL"
Se isso funciona, o resto funciona.
API ou workflows?
Há três formas de conectar e elas não competem: são usadas juntas. A diferença real é quem escreve o código.
| O que você quer fazer | Caminho | Quem faz |
|---|---|---|
| Carregar milhares de clientes e produtos | API + lote | Sua equipe técnica |
| «Negócio ganho → avisar o ERP» | Workflow | Sua equipe comercial, sem código |
| «O ERP avisa que foi pago» | Gatilho de webhook | Uma chamada HTTP, sem credencial |
| Sincronização incremental sem perder nada | API + webhooks | Sua equipe técnica |
Com workflows, sem escrever código do lado da Hapee
Dentro da Hapee, no construtor de workflows, há duas peças que conversam com seu sistema:
- Nó «Webhook de saída» — colocado depois da etapa que lhe interessa
(por exemplo, «o negócio passou para Ganho») e faz um
POSTpara sua URL. Configuram-se a URL, o método, o corpo JSON com variáveis do contato, os headers e um segredo para assinar. - Gatilho «Webhook recebido» — dá a você uma URL para a qual seu sistema faz
POSTpara iniciar o workflow. Não precisa de credencial: basta o token que vai na própria URL.
É o caminho mais rápido para começar: quem já usa a Hapee monta em uma tarde, sem esperar por ninguém.
⚠️ Um 4xx do seu lado NÃO interrompe o workflow. O motor só tenta novamente quando seu
sistema responde 5xx. Se ele responde 401 porque o token está errado, ou
400 porque não gostou do corpo, o workflow segue como se tivesse
funcionado e ninguém percebe.
O código de resposta fica na variável _http_status. Coloque sempre um
nó «Se / Senão» que o verifique logo após o webhook de saída, e envie o caso ruim para
uma notificação interna. Sem isso, no dia em que seu token vencer, as vendas «foram avisadas» e não
chegaram.
No registro de execução do workflow essa etapa aparece marcada como «sem efeito», com o código HTTP ao lado — mas isso só vê quem entra para olhar, e o nó «Se / Senão» avisa sozinho.
Quando a API compensa mais que o workflow
| Workflow | API + webhooks | |
|---|---|---|
| Novas tentativas se seu sistema está fora do ar | 5 tentativas, ~1 hora | 12 tentativas, ~24 horas |
| Registro de entregas consultável | No registro do workflow | Sim, com reenvio manual |
| Um 4xx do seu lado | Segue como se nada tivesse acontecido | É tentado de novo e fica registrado |
| Assinatura | Segredo opcional que você configura | HMAC com carimbo de data e hora |
| Carga em massa | Não | Até 100 por chamada |
| Quem configura | A equipe comercial | A equipe técnica |
Nossa recomendação: comece com workflows para testar a conexão de ponta a ponta, e passe para a API o que não pode ser perdido — cobrança, faturas e a carga inicial.
Fonte da verdade: quem manda em quê
É preciso combinar isto antes de sincronizar qualquer coisa, porque define o que acontece quando os dois lados alteram o mesmo dado. A proposta padrão:
| Entidade | Manda | Motivo |
|---|---|---|
| Dados tributários (RUT, razão social, ramo de atividade) | ERP | É o que vai no DTE |
| Produtos e preços de lista | ERP | É onde o catálogo é mantido |
| Faturas, notas de crédito, pagamentos | ERP | O SII só conhece o ERP |
| Etapa do negócio, atividade comercial | Hapee | É onde a equipe de vendas trabalha |
| Contatos e seu histórico de conversas | Hapee | É onde elas acontecem |
| Dados de contato (telefone, e-mail) | Quem mexeu por último | Os dois são válidos |
O que não pode acontecer é não decidir. Sem este acordo, cada lado sobrescreve o outro e o sintoma aparece semanas depois como «os dados somem».
Fluxo A — Carga inicial
Uma única vez, ao conectar. Use POST /{recurso}/batch com até 100 itens por chamada. O lote não é abortado por um item ruim: entram os que for possível e ele informa quais falharam.
curl -X POST https://beta.hapee.ai/api/v1/companies/batch \
-H "Authorization: Bearer hp_test_SUA_CREDENCIAL" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: carga-inicial-empresas-lote-001" \
-d '{
"items": [
{"external_id": "ERP-CLI-4471", "rut": "76.543.210-K",
"nombre": "Comercial Andes SpA",
"email": "contacto@andes.cl"},
{"external_id": "ERP-CLI-4472", "rut": "77.111.222-3",
"nombre": "Distribuidora Sur Ltda"}
]
}'
{
"job_id": 812,
"total": 2, "succeeded": 2, "failed": 0,
"results": [
{"index": 0, "ok": true, "status": 201, "created": true, "data": {"id": 9001}},
{"index": 1, "ok": true, "status": 201, "created": true, "data": {"id": 9002}}
]
}
Três coisas que poupam uma dor de cabeça
- Envie sempre
external_idcom o id do seu ERP. É o que permite repetir a carga sem duplicar: a segunda execução atualiza em vez de criar ("created": false). - O
job_idpermite reler o resultado comGET /jobs/{id}se você perder a resposta. UmPOSTde 100 itens cujo resultado se perde por uma queda não pode ser repetido às cegas; com o job, você consulta. - Carregue empresas antes dos contatos, para poder associá-los.
Ordem recomendada: companies → product-categories e products → contacts.
Fluxo B — Sincronização incremental
ERP → Hapee
Use upsert, não create. O pareamento é a parte delicada e a precedência é determinística:
external_id > rut > email > phone
Testa-se um critério por vez, em ordem, e vale o primeiro que encontrar algo. Você pode fixá-lo com match_by:
curl -X POST https://beta.hapee.ai/api/v1/contacts/upsert \
-H "Authorization: Bearer hp_test_SUA_CREDENCIAL" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sync-contato-CON-991-v3" \
-d '{
"match_by": "external_id",
"external_id": "ERP-CON-991",
"email": "jefe.compras@andes.cl",
"first_name": "Rodrigo", "last_name": "Pinto",
"phone": "+56912345678",
"custom_fields": {"payment_terms": "30 días"}
}'
match_by é exclusivo. Se você pede para parear por external_id e não há correspondência, ele cria — não recorre ao e-mail. É de propósito: recorrer seria desobedecer você justamente quando foi explícito, e poderia sobrescrever alguém que seu ERP considera outra pessoa.
O RUT é validado. Um dígito verificador errado retorna 422 e nada é salvo. Um RUT mal salvo não identifica ninguém e além disso envenena toda deduplicação futura: a próxima carga com o RUT correto não o encontra e cria uma duplicata.
Hapee → ERP
Duas opções, os mesmos dados. Opção 1 — Webhooks (recomendada se você puder expor uma URL https):
curl -X POST https://beta.hapee.ai/api/v1/webhooks/endpoints \
-H "Authorization: Bearer hp_test_SUA_CREDENCIAL" \
-H "Content-Type: application/json" \
-d '{
"url": "https://erp.suaempresa.com.br/hooks/hapee",
"description": "ERP produção",
"events": ["deal.won", "quote.accepted", "contact.updated", "company.updated"]
}'
A resposta traz o secret uma única vez. Veja Webhooks para a verificação de assinatura.
Opção 2 — GET /events (se o ERP está instalado na empresa, sem porta aberta):
curl "https://beta.hapee.ai/api/v1/events?cursor=10432&limit=100" \
-H "Authorization: Bearer hp_test_SUA_CREDENCIAL"
{
"data": [
{"id": "evt_10433", "type": "deal.won", "api_version": "v1",
"created_at": "2026-09-10T14:02:11Z", "subaccount_id": 63,
"data": {"id": 5521, "title": "Reposição Q4",
"amount": "4200000", "currency": "CLP"}}
],
"next_cursor": "10433",
"has_more": false
}
Guarde o next_cursor e passe-o na chamada seguinte. Os eventos vêm do mais antigo ao mais novo de propósito: você está se atualizando, e é preciso processar na ordem em que ocorreram. Retenção: 30 dias.
Fluxo C — Negócio ganho → fatura
O fluxo que justifica a integração.
Hapee Seu ERP
│
│ A equipe marca o negócio como ganho
│
├──── deal.won ─────────────────►│
│ (webhook ou /events) │
│ │ Cria o pedido de venda
│ │ Emite o DTE junto ao SII
│ │
│◄─── PATCH /deals/{id} ─────────┤ Escreve o folio de volta
│ invoice_number: "F-8842" │
│ payment_status: "pending"│
│ │
│◄─── POST custom-objects/… ────┤ Publica o documento como
│ │ registro visível no CRM
│
│ O executivo VÊ a fatura dentro da ficha do cliente
Passo 1 — Você recebe o evento
deal.won traz o negócio completo, com o mesmo formato que GET /deals/{id} retorna.
Passo 2 — Você escreve de volta
curl -X PATCH https://beta.hapee.ai/api/v1/deals/5521 \
-H "Authorization: Bearer hp_test_SUA_CREDENCIAL" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: folio-F-8842" \
-d '{"custom_fields": {
"invoice_number": "F-8842",
"payment_status": "pending",
"due_date": "2026-10-10"}}'
Passo 3 — Você publica o documento como registro
Assim o executivo vê a fatura sem sair da Hapee:
curl -X POST https://beta.hapee.ai/api/v1/custom-objects/{objeto_id}/records \
-H "Authorization: Bearer hp_test_SUA_CREDENCIAL" \
-H "Content-Type: application/json" \
-d '{
"external_id": "ERP-DTE-8842",
"values": {
"folio": "F-8842", "tipo_dte": "33",
"monto_neto": "3529412", "iva": "670588", "total": "4200000",
"moneda": "CLP", "fecha_emision": "2026-09-10",
"estado": "emitida",
"url_pdf": "https://erp.suaempresa.com.br/dte/8842.pdf"
}
}'
Fluxo D — Cobrança
# 1. O ERP avisa que o documento venceu sem pagamento
curl -X PATCH https://beta.hapee.ai/api/v1/deals/5521 \
-H "Authorization: Bearer hp_test_SUA_CREDENCIAL" \
-d '{"custom_fields": {"payment_status": "overdue", "amount_paid": "0"}}'
# 2. O contato é etiquetado
curl -X POST https://beta.hapee.ai/api/v1/contacts/8821/tags \
-H "Authorization: Bearer hp_test_SUA_CREDENCIAL" \
-d '{"tag": "cobranza-vencida"}'
# 3. O contato é inscrito no workflow de cobrança
curl -X POST https://beta.hapee.ai/api/v1/workflows/44/enroll \
-H "Authorization: Bearer hp_test_SUA_CREDENCIAL" \
-d '{"contacto_id": 8821}'
A partir daí, a Hapee cuida de tudo: os lembretes, o WhatsApp, o e-mail. Quando o cliente paga, o ERP fecha o ciclo com payment_status: "paid" e a etiqueta é removida.
Mapeamento de campos
Modelo para completar com sua equipe. Os campos da Hapee são os que a API expõe hoje.
Empresa / Cliente
| Seu ERP | Hapee | Notas |
|---|---|---|
| (id interno) | external_id | Único por subconta. É a chave da sincronização |
| RUT | rut | O dígito verificador é validado. É salvo como 12345678-K |
| Razão social | razon_social | |
| Nome fantasia | nombre | Obrigatório ao criar |
| Ramo de atividade | custom_fields.giro | |
| E-mail de faturamento | custom_fields.billing_email | Diferente de email |
| Condição de pagamento | custom_fields.payment_terms | |
| Endereço | direccion, ciudad, region |
Contato / Pessoa
| Seu ERP | Hapee | Notas |
|---|---|---|
| (id interno) | external_id | Único por subconta |
| RUT | rut | Opcional; pode se repetir entre contatos |
| Nome / Sobrenome | first_name / last_name | |
email | Em minúsculas | |
| Telefone | phone | E.164: +56912345678 |
| Empresa | empresa_id | O id da Hapee, não o do seu ERP |
Negócio / Pedido de venda
| Seu ERP | Hapee | Notas |
|---|---|---|
| Nº do pedido de venda | external_id | |
| Folio do DTE | custom_fields.invoice_number | |
| Status de pagamento | custom_fields.payment_status | pending / paid / overdue |
| Valor pago | custom_fields.amount_paid | Texto decimal |
| Vencimento | custom_fields.due_date | YYYY-MM-DD |
| Valor | amount + currency | Texto decimal, não número |
Checklist para ir à produção
Antes de trocar hp_test_ por hp_live_:
GET /meresponde e mostra a subconta correta- A carga inicial foi executada duas vezes no sandbox e não duplicou nada
- Todos os registros têm
external_idcom o id do seu ERP - Os RUTs passam na validação (sem
422no lote) - O endpoint de webhook verifica a assinatura e rejeita uma assinatura inválida
- O receptor deduplica pelo
iddo evento (a ordem não é garantida e uma entrega pode se repetir) - Toda escrita envia
Idempotency-Key - Foi testada uma nova tentativa com a mesma chave e não duplicou
- O fluxo C completo funciona de ponta a ponta no sandbox
- A fonte da verdade por entidade está combinada
- Há um lugar onde o ERP registra os erros da API, com o
request_idda resposta - Se o ERP tem IP fixo, ele está carregado na allowlist da credencial
A mudança para produção
- Peça à agência uma credencial
hp_live_da subconta real - Troque a credencial e a URL do webhook
- Execute a carga inicial uma vez
- Verifique com
GET /eventsque os eventos estão chegando
Não é preciso mudar mais nada. O sandbox e a produção usam a mesma API.
Erros frequentes
| Código | O que aconteceu | O que fazer |
|---|---|---|
401 invalid_key | A credencial não existe ou foi revogada | Peça uma nova |
403 ip_no_permitida | Seu IP não está na allowlist | Peça para adicioná-lo |
403 no_es_sandbox | Credencial hp_test_ em subconta real | Use hp_live_ |
404 not_found | Não existe, ou não é da sua subconta | Revise o id e a credencial |
409 idempotency_key_reusada | Mesma chave, corpo diferente | Use uma chave nova |
422 validation_error | Veja details[]: ele diz o campo e o problema | |
429 | Você passou do limite | Aguarde o que diz Retry-After |
Guarde sempre o X-Request-Id da resposta. É o que nos permite encontrar sua chamada exata em nossos registros. Escreva para info@hapee.ai.