Envios de formulário
Cada vez que alguém preenche um formulário da instituição, o Workfuse guarda um envio: o que a pessoa digitou, qual contato foi reconhecido e qual versão dos termos ela aceitou. Esta página cobre a lista de envios, pensada para a sua aplicação consultar de tempos em tempos, e o detalhe de um envio, com o resultado por campo e o termo aceito.
Todos os caminhos são relativos a API_BASE/public/v1/crm, e toda requisição leva a API
key com o módulo crm: Authorization: Bearer wf_sua_chave_aqui. A chave define a
instituição: você só enxerga os envios dela.
Listar envios
GET /form-submissions · síncrono
curl "API_BASE/public/v1/crm/form-submissions?createdFrom=2026-10-01T00:00:00Z&pageSize=50" \
-H "Authorization: Bearer wf_sua_chave_aqui"
Os envios vêm em ordem de createdAt e, no empate, de id. A ordem é estável: paginar
não repete nem pula envio.
Filtros
| Parâmetro | Descrição |
|---|---|
createdFrom | ISO 8601. Inclusivo: traz envios com createdAt >= createdFrom. |
createdTo | ISO 8601. Exclusivo: traz envios com createdAt < createdTo. |
formId | Só os envios deste formulário. |
contactId | Só os envios deste contato (id do Workfuse). |
idProperty | externalId, email ou taxId. Com ele, contactId é lido como o valor desta propriedade. Exige contactId. |
channelConsent | EMAIL, WHATSAPP ou PUSH. Só os envios em que a pessoa marcou a caixa deste canal. |
includeTest | true inclui envios de teste. Padrão: false. |
include | Relações a expandir, separadas por vírgula (include=form,contact) ou com o parâmetro repetido (include=form&include=contact). Veja abaixo. |
page | Padrão 1. |
pageSize | Padrão e máximo 100. Acima disso, 400. |
A janela de datas é meio-aberta ([createdFrom, createdTo)), então janelas consecutivas
não contam o mesmo envio duas vezes. createdFrom depois de createdTo responde 400.
contactId é um filtro, não um recurso: um contato que não existe na instituição devolve
uma página vazia (200), nunca 404.
O que vem na resposta
Sem include, só o envio:
{
"data": [
{
"id": "clx1a2b3c4d5e6f7g8h9i0j1k",
"createdAt": "2026-10-02T10:00:00.000Z",
"isTest": false,
"form": { "id": "clf1a2b3c4d5e6f7g8h9i0j1k" },
"contact": { "id": "clz9q2k8r0001s6vp2h4m7x1e" },
"contactMatch": "EMAIL",
"values": { "email": "ana@exemplo.com", "name": "Ana Souza" },
"termsAcceptance": {
"termsVersion": 3,
"acceptedAt": "2026-10-02T10:00:00.000Z"
}
}
],
"pagination": { "page": 1, "pageSize": 50, "pages": 1, "items": 1 }
}
termsAcceptancevem sempre: é a versão dos termos que a pessoa aceitou ao enviar.contactMatchdiz como o contato foi reconhecido:EMAIL,CPFouNEW_CONTACT. Énullem envios gravados antes desta versão da API, que também vêm comfieldsvazio no detalhe.contacténullquando o envio não tem contato (por exemplo, o contato foi excluído).valuesé o que a pessoa digitou, por chave de propriedade. Um campo opcional deixado vazio vem comonull, nunca como"".
Expandir com include
| Valor | O que traz |
|---|---|
form | form completo: id, name, usageTags, publishedAt e institution ({ id, name }). |
contact | contact com properties, igual ao GET /contacts/{id}. |
contact.consents | Como contact, mais consents (um item por canal). Expande contact junto. |
channelConsents | Os consentimentos que este envio gerou, um item por caixa marcada. |
Um channelConsents vazio ([]) significa que a pessoa não marcou nenhuma caixa. O envio
em si continua válido: os termos ficam em termsAcceptance.
curl "API_BASE/public/v1/crm/form-submissions?include=form,contact.consents,channelConsents" \
-H "Authorization: Bearer wf_sua_chave_aqui"
{
"id": "clx1a2b3c4d5e6f7g8h9i0j1k",
"form": {
"id": "clf1a2b3c4d5e6f7g8h9i0j1k",
"name": "Inscrição 2027",
"usageTags": ["site"],
"publishedAt": "2026-09-20T12:00:00.000Z",
"institution": { "id": "cli1a2b3c4d5e6f7g8h9i0j1k", "name": "Campus Centro" }
},
"contact": {
"id": "clz9q2k8r0001s6vp2h4m7x1e",
"properties": { "name": "Ana Souza", "email": "ana@exemplo.com" },
"consents": [
{ "channel": "WHATSAPP", "status": "GIVEN", "sendable": true },
{ "channel": "EMAIL", "status": "NONE", "sendable": false },
{ "channel": "PUSH", "status": "NONE", "sendable": false }
],
"createdAt": "2026-09-01T12:00:00.000Z",
"updatedAt": "2026-10-02T10:00:00.000Z"
},
"channelConsents": [
{
"id": "clc1a2b3c4d5e6f7g8h9i0j1k",
"channel": "WHATSAPP",
"eventType": "GIVEN",
"address": "+5511999999999",
"origin": { "id": "clo1a2b3c4d5e6f7g8h9i0j1k", "name": "Formulário" },
"consentedAt": "2026-10-02T10:00:00.000Z"
}
]
}
(O exemplo mostra um item de data, sem os campos que já apareceram acima.)
include desconhecido responde 400 com INVALID_INCLUDE e os valores aceitos em
details.accepted. O evidence é do detalhe do envio e não é aceito na lista.
Expandir contact custa uma leitura por contato da página: com pageSize alto, prefira
pedir só o que você usa.
Como sincronizar
- Na primeira vez, peça sem
createdFrome pagine atépagepassar depages. - Guarde o maior
createdAtque você recebeu. - Na próxima consulta, use em
createdFromesse valor menos uma margem de 5 minutos. OcreatedAté marcado quando o envio começa a ser gravado, não quando ele fica visível: um envio que ainda estava gravando na sua consulta anterior pode aparecer depois com umcreatedAtmenor que o maior que você guardou. Sem a margem, ele seria pulado. - Descarte os envios que você já tem, pelo
id: com a margem (e comcreatedFrominclusivo), os envios do fim da janela anterior voltam de propósito.
Ler um envio
GET /form-submissions/{id} · síncrono
curl "API_BASE/public/v1/crm/form-submissions/clx1a2b3c4d5e6f7g8h9i0j1k?include=form,channelConsents" \
-H "Authorization: Bearer wf_sua_chave_aqui"
É o mesmo item da lista, mais fields (o que aconteceu com cada campo) e o texto do termo
aceito em termsAcceptance.termsText:
{
"id": "clx1a2b3c4d5e6f7g8h9i0j1k",
"createdAt": "2026-10-02T10:00:00.000Z",
"isTest": false,
"form": { "id": "clf1a2b3c4d5e6f7g8h9i0j1k", "name": "Inscrição 2027" },
"contact": { "id": "clz9q2k8r0001s6vp2h4m7x1e" },
"contactMatch": "EMAIL",
"values": { "email": "ana@exemplo.com", "city": null },
"fields": [
{
"propertyKey": "email",
"label": "E-mail",
"native": true,
"required": true,
"conflictStrategy": "KEEP_EXISTING",
"submittedValue": "ana@exemplo.com",
"result": "UNCHANGED"
},
{
"propertyKey": "city",
"label": "Cidade",
"native": false,
"required": false,
"conflictStrategy": "OVERWRITE",
"submittedValue": null,
"result": "NOT_APPLIED",
"reason": "NO_VALUE"
}
],
"termsAcceptance": {
"termsVersion": 3,
"termsText": "Aceito receber comunicações da instituição.",
"acceptedAt": "2026-10-02T10:00:00.000Z"
},
"channelConsents": []
}
O que aconteceu com cada campo
fields vem na ordem do formulário. submittedValue é o que a pessoa digitou, ou null
quando veio vazio. result diz o que o envio fez no contato:
result | Significado |
|---|---|
WRITTEN | O valor foi gravado no contato. |
APPENDED | O valor foi somado ao que o contato já tinha. |
UNCHANGED | O contato já tinha esse valor. |
KEPT_EXISTING | O contato já tinha outro valor e a estratégia do campo o manteve. |
NOT_APPLIED | Nada foi gravado; reason explica: MATCH_KEY, PROPERTY_NOT_FOUND, INVALID_VALUE, NO_VALUE ou STRATEGY_NOT_ALLOWED. |
PENDING | O processamento parou antes de confirmar a gravação: o valor pode ou não ter sido gravado. Confira o contato antes de repetir a escrita, sobretudo em COMPLEMENT. |
reason só aparece em NOT_APPLIED.
fields e termsAcceptance são a foto do envio: editar o formulário depois, ou publicar
uma nova versão dos termos, não muda o detalhe de um envio que já aconteceu.
Expandir com include
Os mesmos valores da lista, mais evidence:
| Valor | O que traz |
|---|---|
evidence | Acrescenta ipAddress e userAgent dentro de termsAcceptance. null quando o envio não guardou o dado (envio antigo, ou sem o header). |
Sem evidence, as chaves ipAddress e userAgent não existem na resposta. IP e user
agent são dado pessoal: peça só quando precisar da evidência do aceite.
Um envio de teste lê normalmente pelo id, com isTest: true.
Erros do detalhe
| HTTP | Quando |
|---|---|
400 | include desconhecido (INVALID_INCLUDE, com os aceitos em details.accepted). |
401 | API key ausente, inválida ou revogada. |
403 | A chave não tem o módulo crm no escopo (MODULE_NOT_IN_KEY_SCOPE), a conta de serviço não tem integration.public-crm.use no papel (PERMISSION_DENIED), ou a organização não tem a capability crm (CAPABILITY_NOT_ENABLED). |
404 | O envio não existe ou é de outra instituição (FORM_SUBMISSION_NOT_FOUND). A resposta é a mesma nos dois casos. |
Erros da lista
| HTTP | Quando |
|---|---|
400 | Data inválida ou invertida, pageSize acima de 100, idProperty sem contactId, ou include desconhecido. |
401 | API key ausente, inválida ou revogada. |
403 | A chave não tem o módulo crm no escopo (MODULE_NOT_IN_KEY_SCOPE), a conta de serviço não tem integration.public-crm.use no papel (PERMISSION_DENIED), ou a organização não tem a capability crm (CAPABILITY_NOT_ENABLED). |
Não existe 404 na lista: um envio de outra instituição simplesmente não aparece.