Pular para o conteúdo principal

Eventos

Um evento é algo que aconteceu no seu sistema e que o Workfuse precisa saber: uma inscrição concluída, um boleto pago, uma aula assistida. Ao receber um evento, o Workfuse resolve de que contato ele é e atualiza as propriedades de Contato e de Negócio que a instituição mapeou.

É a diferença para um CRM genérico: lá o evento entra na timeline e alguém monta uma automação para transformar isso em dado. Aqui o mapeamento é parte do evento.

Todos os caminhos são relativos a API_BASE/public/v1/crm.

Antes de enviar: a definição do evento​

Você não envia um evento avulso. A instituição primeiro cria uma definição no painel (Configurações → Eventos), e ela declara três coisas:

  1. O nome interno (inscricao_concluida) — é o que vai no campo eventName.
  2. O schema: que propriedades o payload carrega, de que tipo, e quais são obrigatórias. Tipos aceitos: TEXT, EMAIL, NUMBER, DATE.
  3. Os mapeamentos: qual propriedade do evento atualiza qual propriedade de Contato ou de Negócio.
definição (painel) → POST /crm/events → 201 Created
localiza o negócio → resolve contato → grava as propriedades

Enviar um eventName que não existe, ou cuja definição está inativa, responde 404.

Enviar um evento​

POST /events

curl -X POST "API_BASE/public/v1/crm/events" \
-H "Authorization: Bearer wf_sua_chave_aqui" \
-H "Content-Type: application/json" \
-d '{
"eventName": "inscricao_concluida",
"deliveryId": "insc-2026-88431",
"occurredAt": "2026-09-17T12:58:03.000Z",
"contact": {
"id": "ana.souza@exemplo.com",
"externalId": "aluno-4471",
"properties": {
"curso": "Engenharia Civil",
"origem": "Feira de profissões"
}
},
"deal": {
"externalId": "inscricao-88431",
"properties": {
"valor_da_taxa": 85.5
}
}
}'
CampoTipoObrigatórioDescrição
eventNamestringsimO nome interno da definição.
deliveryIdstringsimA sua chave de idempotência — ver abaixo.
occurredAtstringsimISO 8601, quando o fato aconteceu no seu sistema — não quando você enviou.
contactobjectnão*O contato do evento.
contact.idstringnãoO id do Workfuse, o e-mail ou o CPF/CNPJ. O Workfuse distingue pela forma.
contact.externalIdstringnãoO id do contato no seu sistema.
contact.anonymousIdstringnãoUm token seu para quem ainda não se identificou.
contact.propertiesobjectnãoPropriedades do Contato a gravar. Chave fora do catálogo da instituição é ignorada, sem erro. email, taxId e externalId também são ignorados: para mudar a identidade, use PATCH /contacts.
dealobjectnão*O Negócio do evento. Informado e inexistente nesta instituição, a resposta é 404.
deal.idstringnãoO id do Negócio no Workfuse.
deal.externalIdstringnãoO id do Negócio no seu sistema.
deal.propertiesobjectnãoPropriedades do Negócio a gravar, pelas chaves do catálogo. Chave fora do catálogo é ignorada, sem erro.

* Pelo menos um entre contact e deal precisa trazer id ou externalId (ou contact.anonymousId). Sem isso, a resposta é 422. Com deal.id e deal.externalId juntos, vale o id.

As propriedades de contact.properties e deal.properties são validadas juntas contra o schema da definição. Se a mesma chave vier nos dois lados, vale a do deal.

Resposta 201:

{
"id": "clzd8p3r40004s6vp6c2n5t7b",
"deliveryId": "insc-2026-88431",
"contactResolution": "RESOLVED_EMAIL",
"contactId": "clzc1a2b30001s6vpabcd1234",
"offSchemaKeys": [],
"rejectionReason": null
}

A resolução do contato volta na própria resposta. offSchemaKeys lista as chaves enviadas que a definição não declara, e rejectionReason só vem preenchido em REJECTED_UNKNOWN_CONTACT.

Idempotência — deliveryId​

O deliveryId é seu, e é o que torna o reenvio seguro. A segunda entrega com o mesmo deliveryId não cria um segundo evento e não aplica a propriedade duas vezes — devolve 201 de novo, com o mesmo id da primeira, e não grava a propriedade do Contato duas vezes.

É o mesmo papel do Idempotency-Key que você conhece de outras APIs, com duas diferenças: ele é obrigatório (não opcional), e vai no corpo, não no header — porque é propriedade do evento, não da requisição HTTP.

Use um id que o seu lado já tem e que é estável para aquele fato: o id da inscrição, o id da transação. Nunca um timestamp nem um random por tentativa — aí cada retentativa vira um evento novo.

O escopo é a instituição: duas instituições numerando as entregas a partir de 1 não colidem.

Como o contato é resolvido​

O Workfuse tenta os identificadores nesta ordem, e para no primeiro que casar:

contact.id → contact.externalId → taxId → email → anonymousId

A busca é sempre dentro da instituição da sua API key. O resultado volta na resposta, no campo contactResolution:

contactResolutionSignifica
RESOLVED_IDAchou pelo contact.id (id do Workfuse).
RESOLVED_EXTERNAL_IDAchou pelo externalId.
RESOLVED_TAX_IDAchou pelo CPF/CNPJ enviado em contact.id.
RESOLVED_EMAILAchou pelo e-mail enviado em contact.id.
RESOLVED_ANONYMOUS_IDAchou pelo anonymousId de um evento anterior.
RESOLVED_DEALEvento só com deal: o contato é o primário do Negócio.
CONTACT_CREATEDNão achou, e a definição manda criar.
PENDING_ANONYMOUSNão achou; o evento ficou guardado no anonymousId.
REJECTED_UNKNOWN_CONTACTNão achou, e a definição manda recusar.

Quando não acha ninguém​

A definição escolhe a política, no painel:

PolíticaO que faz
CREATE_CONTACTCria o contato, se vier email ou taxId válido.
HOLD_ANONYMOUSGuarda o evento no anonymousId. Quando um evento posterior com o mesmo anonymousId trouxer um identificador, todos os eventos guardados são religados ao contato de uma vez.
REJECTRecusa, com motivo nomeado. O evento é gravado assim mesmo — item recusado que não deixa rastro é indistinguível de item que nunca chegou.

HOLD_ANONYMOUS é o que serve o fluxo mais comum: o visitante navega anônimo, você manda eventos com um anonymousId do seu lado, e no momento em que ele preenche o formulário o histórico inteiro cola no contato.

Identificador de outra instituição​

Se o identificador casa com um contato que existe, mas em outra instituição, o evento é recusado com 422. É quase sempre API key errada, e tratar como "desconhecido" esconderia um erro real de integração.

Como as propriedades são escritas​

Há dois caminhos para uma propriedade chegar ao Negócio. Pelo mapeamento, que é da definição: você manda properties com as chaves do evento, e a definição diz para onde cada uma vai. Ou direto: as chaves de deal.properties que existem no catálogo de Negócio da instituição são gravadas com o próprio nome. Se os dois mirarem a mesma propriedade, o valor de deal.properties é o que fica. null em deal.properties é ignorado — para limpar uma propriedade, use PATCH /deals.

properties.origem → Contato.origem_do_lead
properties.curso → Negócio.curso_de_interesse

Três regras, valem para os dois caminhos:

  • O modo vem do tipo do destino. Propriedade MULTI_SELECT acrescenta o valor à lista (sem duplicar); todo o resto substitui.
  • REFERENCE e USER não podem ser destino. Uma string vinda de sistema de terceiro não é id confiável de registro interno.
  • Chave fora do schema não é erro. Ela é guardada com o evento e devolvida em offSchemaKeys na resposta — você vê que mandou algo que a definição não declara, sem perder a entrega.

Qual Negócio recebe​

O Negócio é a oportunidade aberta sobre o contato — uma inscrição, uma rematrícula.

O evento escreve só no Negócio de deal.id ou deal.externalId, sempre dentro da instituição da sua API key. Sem deal no corpo, nenhum Negócio é escrito, e as propriedades de Contato são aplicadas do mesmo jeito. O evento nunca cria um Negócio: se o deal informado não existe, a resposta é 404 e nada é gravado. Para criar, use POST /deals.

Mandar só deal, sem contact, funciona: o contato do evento é o primário do Negócio (RESOLVED_DEAL). Mandando os dois, o contato do evento é o de contact.

Erros​

Recusado com 4xx, e nada é gravado:

HTTPerrorCodeQuando
400—Corpo malformado (ex.: sem eventName, deliveryId ou occurredAt).
401—API key ausente, inválida ou revogada.
403—A chave não tem o módulo crm no escopo.
404EVENT_DEFINITION_NOT_FOUNDeventName desconhecido nesta instituição.
404EVENT_DEFINITION_INACTIVEA definição existe, mas está inativa.
404DEAL_NOT_FOUNDO deal informado não existe nesta instituição.
422EVENT_IDENTIFIER_REQUIREDNem contact nem deal trouxeram id ou externalId (ou contact.anonymousId). O formato antigo, com identifiers, cai aqui.
422EVENT_CONTACT_FROM_ANOTHER_INSTITUTIONO identificador casa com um contato de outra instituição.
422EVENT_PAYLOAD_SCHEMA_ERRORPropriedade obrigatória ausente, ou valor que não converte para o tipo declarado. details traz missingKeys e invalidTypeKeys.
429—Limite de requisições excedido, quando os limites entrarem em vigor (hoje em prévia).

Quando dá 4xx, nada foi gravado. Corrija o payload e reenvie com o mesmo deliveryId.

A política REJECT não é erro: o evento é gravado e a resposta é 201, com contactResolution: REJECTED_UNKNOWN_CONTACT e o rejectionReason.

Se você vem do HubSpot​

O equivalente lá é o custom behavioral event (POST /events/v3/send). A ergonomia é a mesma; três diferenças mudam a integração:

No HubSpotAqui
POST /events/v3/sendPOST /crm/events
eventName, occurredAt, propertiesIguais
uuid (deduplicação, opcional)deliveryId — mesmo papel, obrigatório
objectId / email / utk, soltos e com ordem de prioridadecontact{} e deal{}, objetos nomeados
Evento vai para a timeline do contato, com as propriedades do eventoO evento também atualiza propriedade do Contato e do Negócio, pelo mapeamento da definição
Propriedade do registro se atualiza por workflow, à parteO mapeamento é parte da definição do evento

Medido em developers.hubspot.com e knowledge.hubspot.com em 17/09/2026. A documentação do HubSpot descreve o evento indo para a timeline e não declara atualização de propriedade do registro pelo envio — por isso a penúltima linha está escrita como divergência nossa, não como paridade.