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:

  1. A subconta vem da credencial. Nunca é enviada como parâmetro. Uma credencial pertence a uma subconta e só pode ler e escrever nela.
  2. 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.
  3. 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 fazerCaminhoQuem faz
Carregar milhares de clientes e produtosAPI + loteSua equipe técnica
«Negócio ganho → avisar o ERP»WorkflowSua equipe comercial, sem código
«O ERP avisa que foi pago»Gatilho de webhookUma chamada HTTP, sem credencial
Sincronização incremental sem perder nadaAPI + webhooksSua 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:

É 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

WorkflowAPI + webhooks
Novas tentativas se seu sistema está fora do ar5 tentativas, ~1 hora12 tentativas, ~24 horas
Registro de entregas consultávelNo registro do workflowSim, com reenvio manual
Um 4xx do seu ladoSegue como se nada tivesse acontecidoÉ tentado de novo e fica registrado
AssinaturaSegredo opcional que você configuraHMAC com carimbo de data e hora
Carga em massaNãoAté 100 por chamada
Quem configuraA equipe comercialA 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:

EntidadeMandaMotivo
Dados tributários (RUT, razão social, ramo de atividade)ERPÉ o que vai no DTE
Produtos e preços de listaERPÉ onde o catálogo é mantido
Faturas, notas de crédito, pagamentosERPO SII só conhece o ERP
Etapa do negócio, atividade comercialHapeeÉ onde a equipe de vendas trabalha
Contatos e seu histórico de conversasHapeeÉ onde elas acontecem
Dados de contato (telefone, e-mail)Quem mexeu por últimoOs 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

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 ERPHapeeNotas
(id interno)external_idÚnico por subconta. É a chave da sincronização
RUTrutO dígito verificador é validado. É salvo como 12345678-K
Razão socialrazon_social
Nome fantasianombreObrigatório ao criar
Ramo de atividadecustom_fields.giro
E-mail de faturamentocustom_fields.billing_emailDiferente de email
Condição de pagamentocustom_fields.payment_terms
Endereçodireccion, ciudad, region

Contato / Pessoa

Seu ERPHapeeNotas
(id interno)external_idÚnico por subconta
RUTrutOpcional; pode se repetir entre contatos
Nome / Sobrenomefirst_name / last_name
E-mailemailEm minúsculas
TelefonephoneE.164: +56912345678
Empresaempresa_idO id da Hapee, não o do seu ERP

Negócio / Pedido de venda

Seu ERPHapeeNotas
Nº do pedido de vendaexternal_id
Folio do DTEcustom_fields.invoice_number
Status de pagamentocustom_fields.payment_statuspending / paid / overdue
Valor pagocustom_fields.amount_paidTexto decimal
Vencimentocustom_fields.due_dateYYYY-MM-DD
Valoramount + currencyTexto decimal, não número

Checklist para ir à produção

Antes de trocar hp_test_ por hp_live_:

A mudança para produção

  1. Peça à agência uma credencial hp_live_ da subconta real
  2. Troque a credencial e a URL do webhook
  3. Execute a carga inicial uma vez
  4. Verifique com GET /events que os eventos estão chegando

Não é preciso mudar mais nada. O sandbox e a produção usam a mesma API.

Erros frequentes

CódigoO que aconteceuO que fazer
401 invalid_keyA credencial não existe ou foi revogadaPeça uma nova
403 ip_no_permitidaSeu IP não está na allowlistPeça para adicioná-lo
403 no_es_sandboxCredencial hp_test_ em subconta realUse hp_live_
404 not_foundNão existe, ou não é da sua subcontaRevise o id e a credencial
409 idempotency_key_reusadaMesma chave, corpo diferenteUse uma chave nova
422 validation_errorVeja details[]: ele diz o campo e o problema
429Você passou do limiteAguarde 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.