@lionchat/mcp-server 1.12.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/instructions.md +11 -1
- package/dist/docs/resources/api-conventions.md +7 -0
- package/dist/docs/resources/best-practices.md +48 -1
- package/dist/docs/resources/conversation-flows.md +36 -0
- package/dist/docs/resources/data-model.md +25 -0
- package/dist/docs/resources/flowbuilder-design-guide.md +6 -0
- package/dist/docs/resources/flowbuilder-patterns.md +27 -0
- package/dist/docs/resources/formularios-publicos.md +499 -0
- package/dist/docs/resources/glossary.md +49 -1
- package/dist/docs/resources/troubleshooting.md +76 -3
- package/dist/resources.js +6 -0
- package/dist/resources.js.map +1 -1
- package/dist/tools.js +27 -5
- package/dist/tools.js.map +1 -1
- package/endpoints.json +470 -73
- package/package.json +1 -1
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
# Formulários Públicos — Guia Profundo
|
|
2
|
+
|
|
3
|
+
Tudo sobre os Formulários Públicos do LionChat (captação estilo Typeform/Typebot): link público,
|
|
4
|
+
caixa de WhatsApp vinculada, desenho dos blocos, publicação, respostas, funil, simulador e
|
|
5
|
+
integração com fluxos. Atualizado em 18/08/2026 (v4.10.226).
|
|
6
|
+
|
|
7
|
+
## O que é
|
|
8
|
+
|
|
9
|
+
Um formulário público é uma página de captação hospedada pelo próprio LionChat. O visitante abre um
|
|
10
|
+
link, responde bloco a bloco, e o LionChat cria o contato, guarda as respostas, abre a conversa na
|
|
11
|
+
caixa de WhatsApp vinculada e (opcionalmente) dispara um fluxo do FlowBuilder no fim.
|
|
12
|
+
|
|
13
|
+
- **Link público**: `{FRONTEND_URL}/forms/<referência da conta>/<slug>` — a referência da conta é o
|
|
14
|
+
`booking_public_slug` (o mesmo da agenda pública). O `slug` do formulário nasce sorteado (8
|
|
15
|
+
caracteres) e **pode ser trocado** (ver Campos). O campo `public_url` já vem pronto na resposta.
|
|
16
|
+
- **Caixa de WhatsApp vinculada é OBRIGATÓRIA para publicar** (`inbox_id`). Só canais
|
|
17
|
+
`Channel::Whatsapp` (oficial) e `Channel::Waha` (QR Code). É por ela que o bloco Enviar mensagem
|
|
18
|
+
dispara e que a conversa do lead nasce.
|
|
19
|
+
- **Dois formatos de exibição** (`display_mode`): `cards` (um bloco por tela, estilo Typeform) ou
|
|
20
|
+
`chat` (bolhas de conversa, estilo Typebot).
|
|
21
|
+
- **Feature flag `lead_forms`, por conta, nasce desligada.** Com a flag desligada a **página pública
|
|
22
|
+
responde 404** — ninguém consegue preencher. As tools de dashboard (listar, criar, editar,
|
|
23
|
+
publicar, ver respostas, simular) **funcionam mesmo assim**. Dá pra montar tudo antes de ligar a
|
|
24
|
+
flag; não adianta divulgar o link antes.
|
|
25
|
+
- **Escrita é só de administrador.** `create`, `update`, `destroy`, `publish`, `duplicate` e
|
|
26
|
+
`check_embed` passam por `check_admin_authorization?`; usuário não-admin recebe **401**. Leitura
|
|
27
|
+
(`list`, `show`, `stats`, `responses`) e o **simulador** (`test_run`) são liberados a qualquer
|
|
28
|
+
agente da conta.
|
|
29
|
+
|
|
30
|
+
### Não confundir
|
|
31
|
+
|
|
32
|
+
| Nome parecido | O que é de verdade |
|
|
33
|
+
|---|---|
|
|
34
|
+
| **Formulário público** (este documento) | Página de captação hospedada pelo LionChat, com blocos e link próprio |
|
|
35
|
+
| **MetaLeadForm** | Formulário de anúncio da Meta (Facebook/Instagram Lead Ads) — outro módulo |
|
|
36
|
+
| **Flow / FlowBuilder** | Motor de automação de conversa. O formulário *dispara* fluxos, mas não é um fluxo |
|
|
37
|
+
|
|
38
|
+
## Ferramentas disponíveis
|
|
39
|
+
|
|
40
|
+
| Ferramenta | Endpoint | O que faz |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `lionchat_lead_forms_list` | `GET /lead_forms` | Lista os formulários da conta (mais novos primeiro). Não traz `media_urls` |
|
|
43
|
+
| `lionchat_lead_forms_create` | `POST /lead_forms` | Cria. Admin-only |
|
|
44
|
+
| `lionchat_lead_forms_show` | `GET /lead_forms/:id` | Detalhe completo, único lugar que traz `media_urls` |
|
|
45
|
+
| `lionchat_lead_forms_update` | `PATCH /lead_forms/:id` | Edita (inclusive `inbox_id` e `slug`). Admin-only |
|
|
46
|
+
| `lionchat_lead_forms_destroy` | `DELETE /lead_forms/:id` | Exclui (204 sem corpo). Admin-only |
|
|
47
|
+
| `lionchat_lead_forms_publish` | `POST /lead_forms/:id/publish` | Publica: tira a foto do desenho (com a caixa dentro). Admin-only |
|
|
48
|
+
| `lionchat_lead_forms_duplicate` | `POST /lead_forms/:id/duplicate` | Cópia completa com slug novo (copia o `inbox_id`). Admin-only |
|
|
49
|
+
| `lionchat_lead_forms_stats` | `GET /lead_forms/:id/stats` | Números e funil por bloco |
|
|
50
|
+
| `lionchat_lead_forms_responses_list` | `GET /lead_forms/:id/responses` | Lista de respostas (leve, sem as respostas em si) |
|
|
51
|
+
| `lionchat_lead_forms_responses_show` | `GET /lead_forms/:id/responses/:rid` | Detalhe de uma resposta, com o que a pessoa respondeu |
|
|
52
|
+
| `lionchat_lead_forms_test_run` | `POST /lead_forms/:lead_form_id/test_runs` | Simulador: roda o RASCUNHO no motor real, sem deixar rastro. Qualquer agente da conta |
|
|
53
|
+
| `lionchat_lead_forms_check_embed` | `POST /lead_forms/check_embed` | Confere se um site aceita ser exibido em iframe (bloco Página externa). Admin-only |
|
|
54
|
+
|
|
55
|
+
**Imagens não têm ferramenta MCP.** O upload de imagem de bloco (`POST /lead_forms/:id/media`,
|
|
56
|
+
máximo 5 MB, só `image/*`) existe no painel. Pela API dá pra referenciar um `image` que já foi
|
|
57
|
+
subido, nunca subir um novo.
|
|
58
|
+
|
|
59
|
+
## Campos do formulário
|
|
60
|
+
|
|
61
|
+
| Campo | Tipo | Observação |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `name` | string | **Obrigatório** |
|
|
64
|
+
| `display_mode` | string | `cards` (padrão) ou `chat` |
|
|
65
|
+
| `active` | boolean | Formulário inativo devolve 404 na página pública |
|
|
66
|
+
| `inbox_id` | integer | Caixa de WhatsApp vinculada. Obrigatória para publicar. Ver regras abaixo |
|
|
67
|
+
| `form_data` | jsonb | O desenho (rascunho). Teto de 2 MB serializado |
|
|
68
|
+
| `settings` | jsonb | Ver abaixo |
|
|
69
|
+
| `slug` | string | **Editável** em create e update. Normalizado no servidor (acento vira letra, resto vira hífen), 3 a 60 caracteres, minúsculas + números + hífen, único dentro da conta. Vazio/só espaços = mantém o atual (não é erro). Duplicado = 422. Ausente no create = sorteia 8 caracteres |
|
|
70
|
+
| `published_at` | timestamp | Quando a foto foi tirada. Nulo = nunca publicado |
|
|
71
|
+
| `public_url` | string | Montado pelo servidor |
|
|
72
|
+
| `counts` | objeto | `{views, responses, completed}` — contadores do banco |
|
|
73
|
+
|
|
74
|
+
**A resposta de `show`/`list`/`create`/`update` traz também:** `inbox` (`{id, name, channel_type}`
|
|
75
|
+
ou `null` — a caixa resolvida), e `published_inbox_id` (a caixa que está NA FOTO publicada).
|
|
76
|
+
|
|
77
|
+
### Regras do `inbox_id`
|
|
78
|
+
|
|
79
|
+
- Caixa que não é da conta → 422 `{errors: {inbox_id: ["not found in this account"]}}`.
|
|
80
|
+
- Canal fora de `Channel::Whatsapp`/`Channel::Waha` → `["channel not supported"]`.
|
|
81
|
+
- **Trocar de caixa pode; trocar de TIPO de canal não** → `["channel type cannot change"]`.
|
|
82
|
+
Os blocos foram montados pro canal atual (oficial exige modelo aprovado; QR aceita texto livre e
|
|
83
|
+
mídia). Formulário que ainda não tinha caixa escolhe livremente.
|
|
84
|
+
- **A troca só vale depois de republicar**: `publish` grava o `inbox_id` DENTRO da foto
|
|
85
|
+
(`published_data`), e o motor lê sempre a foto.
|
|
86
|
+
|
|
87
|
+
### `settings`
|
|
88
|
+
|
|
89
|
+
| Chave | Valor | Padrão |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `primary_color` | hex | `#7C3AED` |
|
|
92
|
+
| `theme` | `light` ou `dark` (valor fora da lista cai em `light`) | `light` |
|
|
93
|
+
| `abandon_minutes` | inteiro, limitado a 1..15 (minutos) | 15 |
|
|
94
|
+
| `button_label` | string — rótulo do botão da página pública | — |
|
|
95
|
+
| `resume` | booleano — continuar de onde parou entre visitas | `true` |
|
|
96
|
+
| `chat_avatar` | id numérico de blob já subido (`media`) — foto redonda ao lado dos balões, só no formato `chat`. O público recebe `chat_avatar_url` resolvida (nunca o id) | — |
|
|
97
|
+
|
|
98
|
+
## Ciclo de vida
|
|
99
|
+
|
|
100
|
+
1. **Criar** com `lionchat_lead_forms_create` (nome + `form_data`, mesmo que mínimo).
|
|
101
|
+
2. **Vincular a caixa de WhatsApp** (`inbox_id`) — sem ela a publicação recusa (`form_without_inbox`).
|
|
102
|
+
3. **Montar o desenho** em `form_data` (contrato na próxima seção). Todo formulário precisa de uma
|
|
103
|
+
pergunta com `map_to: contact.phone` (`form_requires_phone_question`).
|
|
104
|
+
4. **Testar** com `lionchat_lead_forms_test_run` — roda o rascunho de verdade, sem sujar nada.
|
|
105
|
+
5. **Publicar** com `lionchat_lead_forms_publish` — copia `form_data` (+ `inbox_id`) para
|
|
106
|
+
`published_data` e carimba `published_at`.
|
|
107
|
+
6. **Ativar** (`active: true`) e ligar a feature flag `lead_forms` da conta.
|
|
108
|
+
7. **Divulgar** o `public_url`.
|
|
109
|
+
|
|
110
|
+
**Quem preenche usa SEMPRE a foto publicada, nunca o rascunho.** Editar `form_data` não muda nada
|
|
111
|
+
para quem está preenchendo até um novo `publish`.
|
|
112
|
+
|
|
113
|
+
**O contato nasce em TRÊS momentos**: quando a pessoa conclui, quando a resposta é marcada como
|
|
114
|
+
abandonada (padrão e teto de 15 minutos sem atividade), ou logo antes de um bloco de Enviar
|
|
115
|
+
WhatsApp. Enquanto isso nada é criado — nem contato, nem conversa, nem card. Efeitos que dependem de
|
|
116
|
+
contato (evento de pixel, gatilho de marco) ficam guardados e disparam no nascimento, com a hora
|
|
117
|
+
original de cada etapa. Quando o contato nasce ali, a conversa começa com uma pílula dizendo qual
|
|
118
|
+
formulário o originou. Contato apagado no painel solta o vínculo — a resposta pode renascer no
|
|
119
|
+
próximo momento. Os textos `{{contact.name}}`/`{{contact.phone}}`/`{{contact.email}}` saem do que a
|
|
120
|
+
pessoa RESPONDEU, não da ficha.
|
|
121
|
+
|
|
122
|
+
### Link identificado (pula perguntas já conhecidas)
|
|
123
|
+
|
|
124
|
+
Toda mensagem de TEXTO enviada numa conversa que contenha um link `/forms/` sai **carimbada
|
|
125
|
+
automaticamente** com `?lt_form=<código>` (sem depender de LionTrack; vale pra mensagem de flow e
|
|
126
|
+
de IA também; nunca em grupo). Quem abre o link carimbado tem a resposta ligada ao contato desde o
|
|
127
|
+
início, e o formulário **PULA as perguntas de identidade que já têm valor** — só as três nativas:
|
|
128
|
+
nome, telefone e e-mail. A resposta pulada é gravada com o valor da ficha e o selo `prefilled`.
|
|
129
|
+
|
|
130
|
+
Seguranças: o valor pulado **nunca aparece na tela nem no payload público**, e o botão Voltar **não
|
|
131
|
+
alcança** a pergunta pulada. Código de outra conta, contato apagado ou qualquer pane = o formulário
|
|
132
|
+
roda anônimo normal. Link copiado do editor e colado fora de uma conversa NÃO tem carimbo.
|
|
133
|
+
|
|
134
|
+
## O contrato do `form_data`
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"nodes": [
|
|
139
|
+
{ "id": "n1", "type": "question", "position": { "x": 0, "y": 200 }, "data": { } }
|
|
140
|
+
],
|
|
141
|
+
"edges": [
|
|
142
|
+
{ "id": "e1", "source": "n1", "target": "n2", "sourceHandle": "success" }
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- `id` de nó e de aresta: qualquer string única dentro do desenho.
|
|
148
|
+
- `position`: `{x, y}` em pixels do canvas. Só afeta a visualização no editor; o motor ignora.
|
|
149
|
+
- `sourceHandle`: **qual saída do nó** a aresta usa. É o que decide o caminho (ver tabela).
|
|
150
|
+
|
|
151
|
+
### Tipos de bloco
|
|
152
|
+
|
|
153
|
+
| `type` | Chaves de `data` permitidas | Saídas (`sourceHandle`) | Aparece pro lead? |
|
|
154
|
+
|---|---|---|---|
|
|
155
|
+
| `start` | `title, description, button_label, image, video_url` | `success` | sim |
|
|
156
|
+
| `question` | `label, description, field_type, placeholder, required, map_to, scale, image, video_url, accept, invalid_message` | `success` | sim |
|
|
157
|
+
| `choice` | `label, description, options, multiple, map_to` | `option_<id da opção>` (escolha única) / `success` (múltipla) | sim |
|
|
158
|
+
| `message` | `title, description, button_label, image, video_url` | `success` | sim |
|
|
159
|
+
| `condition` | `branches` | `cond_<id do ramo>` + `else` | não |
|
|
160
|
+
| `action` | `items, label` (+ formato antigo `action_type, labels, name, event_name`) | `success` | não |
|
|
161
|
+
| `send_message` | `messageItems` (+ formato antigo `content, template_name, template_params`) | `success`, `skipped` | não |
|
|
162
|
+
| `booking` | `label, description, event_type_id, required` | `success` (agendou) + `no_booking` (seguiu sem agendar; some quando `required`) | sim |
|
|
163
|
+
| `set_variable` | `label, assignments` | `success` | não |
|
|
164
|
+
| `embed` | `title, description, url, height, button_label, embed_allowed, show_open_external` | `success` | sim |
|
|
165
|
+
| `end` | `title, description, redirect_url, button_label, image, video_url` | — | sim |
|
|
166
|
+
| `api_request` | `label, method, url, headers, body, timeout` | `success`, `error` | não (mostra bloco de espera) |
|
|
167
|
+
| `ai` | `label, mode, prompt, categories, include_answers, model` | modo `generate`: `success`, `error` / modo `classify`: um `cat_<id>` por categoria + `other` | não (mostra bloco de espera) |
|
|
168
|
+
|
|
169
|
+
**Chave fora da lista não é lida pelo motor** e o editor a **remove no próximo salvamento pela
|
|
170
|
+
tela**. Escrever um campo inventado pela API não gera erro nenhum — ele simplesmente não faz nada e
|
|
171
|
+
depois some. A lista é fonte única (`LeadForm::NODE_DATA_KEYS` no servidor, espelhada em
|
|
172
|
+
`nodeSchema.js` no editor, com spec de contrato comparando as duas).
|
|
173
|
+
|
|
174
|
+
### Detalhes por chave
|
|
175
|
+
|
|
176
|
+
**`field_type`** (do `question`): `short_text`, `long_text`, `email`, `phone`, `date`, `time`,
|
|
177
|
+
`datetime`, `number`, `rating` (usa `scale`), `url`, `file_upload` (usa `accept`), e os documentos
|
|
178
|
+
com validação de verdade: `cpf`, `cnpj`, `cep` (dígito verificador/8 dígitos; gravam só números;
|
|
179
|
+
erros `invalid_cpf`/`invalid_cnpj`/`invalid_cep`).
|
|
180
|
+
- `time` grava `HH:MM` (24h; erro `invalid_time`).
|
|
181
|
+
- `datetime` grava `AAAA-MM-DDTHH:MM` — hora local de quem preencheu, SEM fuso; o fuso de leitura é
|
|
182
|
+
o da definição do atributo de destino (erro `invalid_datetime`).
|
|
183
|
+
|
|
184
|
+
**`video_url`** (em `start`, `question`, `message`, `end`): link de YouTube, Vimeo ou arquivo de
|
|
185
|
+
vídeo direto. Com vídeo válido, ele ocupa o lugar da imagem. Link fora do formato conhecido não
|
|
186
|
+
renderiza e não dá erro.
|
|
187
|
+
|
|
188
|
+
**`accept`** (só em `file_upload`): `'any'` ou um array de categorias entre `image`, `video`,
|
|
189
|
+
`audio`, `pdf`, `spreadsheet`, `document`, `xml`. Arquivo executável é recusado sempre.
|
|
190
|
+
|
|
191
|
+
**`map_to`** — para onde vai a resposta:
|
|
192
|
+
- `''` — não guarda em lugar nenhum (só em `answers`).
|
|
193
|
+
- `contact.name`, `contact.email`, `contact.phone` — os três nativos.
|
|
194
|
+
- `contact.cadastral.<campo>` — `cpf`, `rg`, `cnpj`, `passport`, `profession`, `marital_status`,
|
|
195
|
+
`gender`, `date_of_birth` (os dois últimos sem tela; funcionam por API e são normalizados:
|
|
196
|
+
"solteira" vira `solteiro`, "masculino" vira `m`).
|
|
197
|
+
- `contact.address.<campo>` — `cep`, `street`, `number`, `complement`, `neighborhood`, `city`,
|
|
198
|
+
`state`, `country`.
|
|
199
|
+
- `custom.<chave>` — atributo personalizado de CONTATO.
|
|
200
|
+
- `conversation_custom.<chave>` — atributo personalizado da CONVERSA (só chave definida na conta;
|
|
201
|
+
coerção de tipo; teto 1500 caracteres).
|
|
202
|
+
- **Lista de recusa no servidor** (o valor é pulado de forma visível, sem erro): `name`, `email`,
|
|
203
|
+
`phone_number`, `identifier`, `card` e chaves começando com `captain_`, `eclinica_`, `origin_`,
|
|
204
|
+
`lt_`, `booking_`, `waha_`, `whatsapp_`, `ctwa_`, `meta_lead_`.
|
|
205
|
+
- Contato pré-existente recebe valor **só em campo vazio** — exceto o que ESTA resposta gravou e
|
|
206
|
+
ninguém mudou depois (correção via Voltar).
|
|
207
|
+
|
|
208
|
+
**`options`** (do `choice`): `[{ "id": "abc123", "label": "Sim", "image": "" }]`.
|
|
209
|
+
|
|
210
|
+
**`branches`** (do `condition`): lista avaliada em ordem; nenhum ramo casando cai no handle `else`.
|
|
211
|
+
Operadores: `equals`, `not_equals`, `contains`, `filled`, `not_filled`, `greater_than`,
|
|
212
|
+
`less_than`. Fonte `api_request` exige `path` (caminho dentro da resposta).
|
|
213
|
+
|
|
214
|
+
**`items`** (do `action`) — o formato novo, uma LISTA de ações `[{key, config}]`:
|
|
215
|
+
|
|
216
|
+
| `key` | `config` mínimo para publicar | O que faz |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| `update_contact_attribute` | `attr_key` | Grava atributo personalizado do CONTATO |
|
|
219
|
+
| `update_conversation_attribute` | `attr_key` | Grava atributo personalizado da CONVERSA |
|
|
220
|
+
| `add_label` | `labels` com ≥1 título | Etiqueta o contato |
|
|
221
|
+
| `milestone` | `name` | Marco do funil (pode disparar fluxo) |
|
|
222
|
+
| `meta_pixel_event` | `event_name` | Evento Meta CAPI (`[a-zA-Z0-9_]`, até 40) |
|
|
223
|
+
| `ga4_event` | `event_name` | Evento GA4 |
|
|
224
|
+
|
|
225
|
+
O `config` das ações de atributo aceita também `attr_value` (texto com variáveis) e `attr_op`:
|
|
226
|
+
`set` (sobrescrever, padrão), `plus`, `minus`, `times`, `divided_by`. **A conta é feita no
|
|
227
|
+
servidor** sobre o valor atual, com trava de repetição (roda UMA vez por preenchimento); valor que
|
|
228
|
+
não vira número não grava; divisão por zero não grava. A operação aritmética é a única que passa
|
|
229
|
+
por cima do "só grava em campo vazio". Identidade (nome/telefone/e-mail) NÃO se grava por aqui.
|
|
230
|
+
Formato antigo (`action_type` + campos soltos) continua sendo lido.
|
|
231
|
+
|
|
232
|
+
**`messageItems`** (do `send_message`) — LISTA de mensagens, enviadas na ordem:
|
|
233
|
+
- `{type: "text", content: "..."}` — texto com variáveis (caixa QR).
|
|
234
|
+
- `{type: "whatsapp_template" | "template", template_params: {name, category, language, namespace,
|
|
235
|
+
processed_params}}` — modelo aprovado (caixa oficial; `language` padrão `pt_BR`).
|
|
236
|
+
- `{type: "image" | "video" | "audio" | "file" | "document" | "attachment" | "url_media",
|
|
237
|
+
url | file_url | blob_signed_id, caption}` — mídia.
|
|
238
|
+
Item que falha **não derruba os outros** — vira `skipped_item {index, reason}` em `answers`.
|
|
239
|
+
A caixa é a DO FORMULÁRIO (`inbox_id`) — o bloco não tem caixa própria. Formato antigo
|
|
240
|
+
(`content`/`template_name`/`template_params` soltos) continua sendo lido.
|
|
241
|
+
|
|
242
|
+
**`booking`** — calendário DENTRO do formulário:
|
|
243
|
+
- `event_type_id` = id de um tipo de evento de Agendamento da conta (ativo).
|
|
244
|
+
- `required: true` = só avança depois de agendar (a saída `no_booking` some do desenho).
|
|
245
|
+
- O payload público leva `booking` (slug público, título, duração, `task_type`, `ask_email`,
|
|
246
|
+
`ask_description`), `prefill` (nome/telefone/e-mail já respondidos) e `missing` (o que o tipo
|
|
247
|
+
exige e o formulário não perguntou — e-mail só quando `task_type` é `video_call`). O id interno
|
|
248
|
+
do tipo NUNCA sai. Tipo apagado/desativado depois de publicado: a tela mostra "indisponível" com
|
|
249
|
+
botão de continuar (o `required` é liberado — ninguém fica preso).
|
|
250
|
+
- O valor gravado na resposta é o id do agendamento criado (validado contra a conta).
|
|
251
|
+
|
|
252
|
+
**`set_variable`** — cria variáveis temporárias da resposta (invisível pro lead):
|
|
253
|
+
- `assignments: [{key, value}]` — até 20 linhas, 1000 caracteres por valor; `key` vira
|
|
254
|
+
`{{var.<key>}}` nos blocos seguintes. Linha com `key` vazia é ignorada.
|
|
255
|
+
- SÓ variável. Gravar em contato/conversa é papel do bloco de Ações (um `target` em um item é
|
|
256
|
+
ignorado pelo motor e descartado pela tela).
|
|
257
|
+
|
|
258
|
+
**`embed`** — "Página externa", um site de fora exibido DENTRO do formulário (checkout, simulador,
|
|
259
|
+
agenda externa):
|
|
260
|
+
- `url` precisa começar com `https://` LITERAL (variável só do meio pra frente). Na montagem, as
|
|
261
|
+
variáveis resolvem por posição, com codificação de URL, e o https é reconferido — URL que não
|
|
262
|
+
for https sai VAZIA do payload (proteção contra script via variável).
|
|
263
|
+
- `embed_allowed` NÃO é configuração sua: é o veredito de `lionchat_lead_forms_check_embed`
|
|
264
|
+
gravado no desenho (`true`/`false`/`null`). Com `false`, a página mostra o aviso educado + botão
|
|
265
|
+
"Abrir em nova página" em vez de um quadrado vazio. Escrever `true` na mão mente pro renderizador.
|
|
266
|
+
- `height` em pixels (padrão 480, limitado 240..1200). `show_open_external` (padrão `true`) mostra
|
|
267
|
+
o botão de abrir fora; com o site recusando o iframe, o botão aparece mesmo desligado — é a
|
|
268
|
+
única saída da pessoa.
|
|
269
|
+
- O bloco avança por botão (a página embutida é caixa fechada — não dá pra saber quando um
|
|
270
|
+
pagamento terminou lá dentro). Checkout é justamente o tipo de página que MAIS recusa iframe.
|
|
271
|
+
|
|
272
|
+
**`api_request`**: `method` aceita `GET`, `POST`, `PUT`, `PATCH`, `DELETE` (padrão `GET`). `body`
|
|
273
|
+
(até 64 KB) aceita variáveis — inclusive `{{conta.<variável>}}`; valor do lead entra escapado em
|
|
274
|
+
corpo JSON. A `url` precisa ter **domínio literal** (variáveis só no caminho e nos valores de
|
|
275
|
+
query; `{{conta.*}}` NÃO vale na URL). `headers` aceita literal ou `{{conta.<variável>}}` —
|
|
276
|
+
variável do lead em cabeçalho é recusada na publicação. `timeout` em segundos: padrão 8, teto 15.
|
|
277
|
+
|
|
278
|
+
**`ai`**: `mode` é `generate` (devolve texto em `{{ia.<node_id>}}`) ou `classify` (roteia por
|
|
279
|
+
categoria). `categories` é obrigatório no modo `classify`. `include_answers` (booleano) manda as
|
|
280
|
+
respostas já dadas como contexto. `model` é validado contra a lista suportada; `''` = modelo padrão
|
|
281
|
+
da conta. A IA usa a **chave da própria conta** — conta sem chave faz o bloco sair pela saída de
|
|
282
|
+
erro.
|
|
283
|
+
|
|
284
|
+
### Variáveis
|
|
285
|
+
|
|
286
|
+
| Variável | O que traz | Onde vale |
|
|
287
|
+
|---|---|---|
|
|
288
|
+
| `{{answer.<node_id>}}` | O que a pessoa respondeu naquele bloco (rótulo da opção ou texto) | mensagem, prompt, valores do bloco, URL/corpo de consulta |
|
|
289
|
+
| `{{contact.name}}` `{{contact.phone}}` `{{contact.email}}` | O que a pessoa respondeu (a ficha é reserva) | idem |
|
|
290
|
+
| `{{api.<node_id>.<caminho>}}` | Um pedaço da resposta de uma consulta | idem |
|
|
291
|
+
| `{{ia.<node_id>}}` | O texto que a IA gerou naquele bloco | idem |
|
|
292
|
+
| `{{var.<nome>}}` | Variável criada pelo bloco Definir variável | idem |
|
|
293
|
+
| `{{conta.<variável>}}` | Variável da conta (cofre) | cabeçalho e CORPO de `api_request` (com segredo); em `set_variable` e no valor das Ações só as NÃO-secretas |
|
|
294
|
+
|
|
295
|
+
Variável desconhecida vira string vazia, em silêncio. `{{conta.*}}` em texto público sai vazia
|
|
296
|
+
mesmo que exista — é proteção, não defeito.
|
|
297
|
+
|
|
298
|
+
## Regras de publicação
|
|
299
|
+
|
|
300
|
+
`lionchat_lead_forms_publish` devolve **422 com `{errors: [chaves]}`**. As 34 chaves atuais:
|
|
301
|
+
|
|
302
|
+
| Chave | Significado |
|
|
303
|
+
|---|---|
|
|
304
|
+
| `no_start` | Não existe bloco `start` |
|
|
305
|
+
| `start_without_exit` | O `start` não tem aresta saindo |
|
|
306
|
+
| `no_reachable_end` | Nenhum `end` alcançável a partir do `start` |
|
|
307
|
+
| `form_without_inbox` | O formulário não tem caixa de WhatsApp válida vinculada |
|
|
308
|
+
| `form_requires_phone_question` | Nenhuma pergunta guarda o telefone (`map_to: contact.phone`) — obrigatória em TODO formulário |
|
|
309
|
+
| `question_without_label` | Pergunta sem o texto |
|
|
310
|
+
| `question_without_exit` | Pergunta sem saída |
|
|
311
|
+
| `choice_without_options` | Escolha sem opções |
|
|
312
|
+
| `choice_without_exit` | Escolha sem nenhuma saída ligada |
|
|
313
|
+
| `choice_option_without_edge` | Uma opção específica não leva a lugar nenhum |
|
|
314
|
+
| `condition_without_branches` | Condição sem ramos |
|
|
315
|
+
| `condition_without_else` | Condição sem o "senão" ligado |
|
|
316
|
+
| `action_without_config` | Bloco de ação sem nenhum item completo |
|
|
317
|
+
| `send_message_official_without_template` | Caixa OFICIAL vinculada e o bloco de mensagem sem nenhum modelo |
|
|
318
|
+
| `send_message_qr_without_content` | Caixa QR vinculada e o bloco de mensagem sem nenhum item que entregue algo |
|
|
319
|
+
| `api_without_url` | Consulta sem endereço |
|
|
320
|
+
| `api_without_error_exit` | Consulta sem a saída de erro ligada |
|
|
321
|
+
| `api_url_host_not_literal` | O domínio da consulta tem variável |
|
|
322
|
+
| `api_method_invalid` | `method` fora de GET/POST/PUT/PATCH/DELETE |
|
|
323
|
+
| `unknown_variable_in_url` | A URL usa variável de família desconhecida |
|
|
324
|
+
| `unknown_variable_in_body` | O corpo usa variável de família desconhecida (ou `{{` malformado) |
|
|
325
|
+
| `account_variable_in_url` | A URL usa `{{conta.*}}` (só cabeçalho e corpo aceitam) |
|
|
326
|
+
| `lead_variable_in_header` | Cabeçalho usa variável vinda do lead |
|
|
327
|
+
| `ai_without_prompt` | IA sem instrução |
|
|
328
|
+
| `ai_without_categories` | IA em modo classificar sem categorias |
|
|
329
|
+
| `ai_without_error_exit` | IA (gerar) sem a saída de erro |
|
|
330
|
+
| `ai_without_other_exit` | IA (classificar) sem a saída "outros" |
|
|
331
|
+
| `booking_without_event_type` | Agendamento sem tipo de evento escolhido |
|
|
332
|
+
| `booking_event_type_invalid` | O tipo não é da conta ou está desativado |
|
|
333
|
+
| `booking_without_exit` | Agendamento sem a saída "agendou" ligada |
|
|
334
|
+
| `set_variable_without_assignments` | Definir variável sem nenhuma linha com nome |
|
|
335
|
+
| `set_variable_without_exit` | Definir variável sem saída |
|
|
336
|
+
| `embed_without_url` | Página externa sem endereço |
|
|
337
|
+
| `embed_url_not_https` | O endereço da Página externa não começa com `https://` literal |
|
|
338
|
+
|
|
339
|
+
**Não existem mais** (renomeadas em 16/08): `send_message_without_inbox` →
|
|
340
|
+
`form_without_inbox`; `send_message_requires_phone_question` → `form_requires_phone_question`.
|
|
341
|
+
|
|
342
|
+
## Tetos e limites
|
|
343
|
+
|
|
344
|
+
| O quê | Limite |
|
|
345
|
+
|---|---|
|
|
346
|
+
| `form_data` serializado | 2 MB |
|
|
347
|
+
| Valor de uma resposta | 4 KB |
|
|
348
|
+
| Passos por requisição (anti-loop) | 50 |
|
|
349
|
+
| `days` em `stats` e `responses` | máximo 90 (valor ≤ 0 vira 30) |
|
|
350
|
+
| Página de `responses` | 25 por página, fixo; `page` até 10.000 |
|
|
351
|
+
| Chamadas de IA | 300 por formulário/dia, 1.000 por conta/dia, 10 por resposta |
|
|
352
|
+
| Consultas (`api_request`) | 2.000 por formulário/dia, 10.000 por conta/dia; corpo até 64 KB |
|
|
353
|
+
| Arquivo enviado pelo lead | 10 MB por arquivo, 10 arquivos por resposta |
|
|
354
|
+
| Imagem de bloco (só pelo painel) | 5 MB, apenas `image/*` |
|
|
355
|
+
| Funil em `stats` | 50.000 respostas (acima disso o funil sai parcial) |
|
|
356
|
+
| Itens do bloco de Ações / linhas do Definir variável | 20 cada; 1000 caracteres por valor |
|
|
357
|
+
| Respostas por chamada do simulador | 60 |
|
|
358
|
+
| Altura da Página externa | 240..1200 px |
|
|
359
|
+
| Conferência de embed | 5 s por salto, até 3 redirecionamentos |
|
|
360
|
+
|
|
361
|
+
## Simulador (`lionchat_lead_forms_test_run`)
|
|
362
|
+
|
|
363
|
+
Roda o **rascunho** (`form_data`, não a foto) no motor de verdade e desfaz tudo no fim — nada fica
|
|
364
|
+
no banco: sem contato, sem conversa, sem WhatsApp, sem pixel, sem resposta nos relatórios. Não
|
|
365
|
+
exige admin. O estado do teste vive em quem chama: cada chamada reproduz o teste inteiro a partir
|
|
366
|
+
da lista de respostas.
|
|
367
|
+
|
|
368
|
+
- Body: `{answers: [{node_id, value}]}` — até 60 itens; vazio = começa do início. Item cujo
|
|
369
|
+
`node_id` não é o bloco atual PARA a reprodução ali (devolve o bloco onde parou).
|
|
370
|
+
- Resposta 200: `{block, log, answers, variables, form}` — `block` é o mesmo envelope da página
|
|
371
|
+
pública; `log` é o caminho percorrido (`[{node_id, type, label, answer, detail}]` — o `detail`
|
|
372
|
+
mostra variável criada, ações executadas, resultado de consulta/IA e o rumo das condições).
|
|
373
|
+
- 422: `{error: "invalid_value", reason}` (uma resposta reprovada na validação) ou
|
|
374
|
+
`{error: "test_run_failed", reason}`.
|
|
375
|
+
|
|
376
|
+
## Respostas e funil
|
|
377
|
+
|
|
378
|
+
**`lionchat_lead_forms_stats`** (`days` opcional) devolve views/starts/completed/abandoned/
|
|
379
|
+
completion_rate + `funnel` por bloco + `milestones`. O funil lista os blocos que a pessoa vê
|
|
380
|
+
(`start`, `question`, `choice`, `message`, `booking`, `embed`, `end`), na ordem do desenho
|
|
381
|
+
**publicado**.
|
|
382
|
+
|
|
383
|
+
**`lionchat_lead_forms_responses_list`** aceita `status` (`in_progress`, `completed`,
|
|
384
|
+
`abandoned`), `days` e `page`. A lista nunca traz as respostas em si — só `{id, status, contact,
|
|
385
|
+
current_node_label, answered_count, created_at, completed_at}`. **`contact` pode vir
|
|
386
|
+
`{id: null, name, phone, pending: true}`** — é quem ainda está preenchendo (o contato só nasce nos
|
|
387
|
+
três momentos); nome e telefone saem do que a pessoa já digitou.
|
|
388
|
+
|
|
389
|
+
**`lionchat_lead_forms_responses_show`** traz o conteúdo:
|
|
390
|
+
|
|
391
|
+
- `answers`: `[{label, value_label, at, download_url?}]`. **Atenção:** o bloco de Ações grava
|
|
392
|
+
entradas SINTÉTICAS com chave `"<node_id>::<n>"` — nem toda chave de `answers` é id de bloco.
|
|
393
|
+
- `conversation`: `{id, display_id}` ou `null` — a conversa aberta na caixa vinculada.
|
|
394
|
+
- `milestones`: os marcos atingidos.
|
|
395
|
+
- `effects`: resultado de consulta/IA — `{node_id, label, state, error, status, url_short, at}`.
|
|
396
|
+
`url_short` é esquema + host + caminho, sem a query; corpo, cabeçalhos e prompt nunca saem.
|
|
397
|
+
- `utm_params`: de onde o lead veio.
|
|
398
|
+
|
|
399
|
+
Motivos de erro em `effects`: `http_<código>`, `timeout`, `ssrf_blocked`, `invalid_url`,
|
|
400
|
+
`result_too_large`, `stale`, `busy`, `budget_exceeded`, `no_ai_key`, `ai_error`,
|
|
401
|
+
`unresolved_variable`.
|
|
402
|
+
|
|
403
|
+
## Conferência de iframe (`lionchat_lead_forms_check_embed`)
|
|
404
|
+
|
|
405
|
+
Body `{url}`. Resposta `{status, reason}`:
|
|
406
|
+
|
|
407
|
+
| `status` | `reason` | Significado |
|
|
408
|
+
|---|---|---|
|
|
409
|
+
| `allowed` | — | O site aceita ser exibido dentro do formulário |
|
|
410
|
+
| `blocked` | `not_https` | URL não começa com https:// |
|
|
411
|
+
| `blocked` | `x_frame_options` / `frame_ancestors` | O site declara que recusa iframe |
|
|
412
|
+
| `blocked` | `url_blocked` | Endereço bloqueado por segurança (rede interna etc.) |
|
|
413
|
+
| `unknown` | `unreachable` / `http_<código>` | Não deu pra conferir — a página tenta o iframe e o botão de abrir fora cobre |
|
|
414
|
+
|
|
415
|
+
O veredito deve ser gravado em `embed_allowed` do bloco. Sites de checkout são os que mais recusam.
|
|
416
|
+
|
|
417
|
+
## Receitas
|
|
418
|
+
|
|
419
|
+
### 1. Formulário simples que já publica
|
|
420
|
+
|
|
421
|
+
Início, nome, telefone, fim — E a caixa vinculada:
|
|
422
|
+
|
|
423
|
+
```json
|
|
424
|
+
{
|
|
425
|
+
"nodes": [
|
|
426
|
+
{ "id": "start", "type": "start", "position": { "x": 0, "y": 0 },
|
|
427
|
+
"data": { "title": "Fale com a gente", "description": "Leva 30 segundos.",
|
|
428
|
+
"button_label": "Começar" } },
|
|
429
|
+
{ "id": "q_nome", "type": "question", "position": { "x": 0, "y": 200 },
|
|
430
|
+
"data": { "label": "Qual é o seu nome?", "field_type": "short_text",
|
|
431
|
+
"required": true, "map_to": "contact.name" } },
|
|
432
|
+
{ "id": "q_fone", "type": "question", "position": { "x": 0, "y": 400 },
|
|
433
|
+
"data": { "label": "Qual é o seu WhatsApp?", "field_type": "phone",
|
|
434
|
+
"required": true, "map_to": "contact.phone" } },
|
|
435
|
+
{ "id": "fim", "type": "end", "position": { "x": 0, "y": 600 },
|
|
436
|
+
"data": { "title": "Obrigado!", "description": "Já falamos com você." } }
|
|
437
|
+
],
|
|
438
|
+
"edges": [
|
|
439
|
+
{ "id": "e1", "source": "start", "target": "q_nome", "sourceHandle": "success" },
|
|
440
|
+
{ "id": "e2", "source": "q_nome", "target": "q_fone", "sourceHandle": "success" },
|
|
441
|
+
{ "id": "e3", "source": "q_fone", "target": "fim", "sourceHandle": "success" }
|
|
442
|
+
]
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Mande isso em `lionchat_lead_forms_create` junto com `name` e um `inbox_id` de caixa WhatsApp,
|
|
447
|
+
depois `lionchat_lead_forms_publish` e `lionchat_lead_forms_update` com `active: true`.
|
|
448
|
+
|
|
449
|
+
### 2. Consultar o CEP e ramificar pela cidade
|
|
450
|
+
|
|
451
|
+
Igual à receita clássica: pergunta o CEP, consulta o ViaCEP (`method` pode ficar de fora — GET é o
|
|
452
|
+
padrão) e ramifica com `path` no ramo cuja fonte é a consulta. Obrigatórios: saída `error` ligada,
|
|
453
|
+
`else` ligado e o `path`.
|
|
454
|
+
|
|
455
|
+
### 3. Triagem por IA
|
|
456
|
+
|
|
457
|
+
`mode: "classify"` + `categories` + arestas `cat_<id>` e `other`. Inalterado.
|
|
458
|
+
|
|
459
|
+
### 4. Disparar um fluxo quando o formulário for concluído
|
|
460
|
+
|
|
461
|
+
O gatilho mora no **fluxo**: `{ "key": "lead_form_completed", "lead_form_id": 12 }` no bloco de
|
|
462
|
+
início do flow. Três gatilhos: `lead_form_completed`, `lead_form_milestone` (aceita
|
|
463
|
+
`milestone_node_id`), `lead_form_abandoned`. O `lead_form_id` pode ir no topo do item ou em
|
|
464
|
+
`config`. **O gatilho só dispara depois que o contato nasce** — antes fica guardado. O fluxo
|
|
465
|
+
recebe `form_name`, `form_kind`, `form_milestone` e uma variável `form_<pergunta>` por resposta.
|
|
466
|
+
|
|
467
|
+
### 5. Agendamento dentro do formulário
|
|
468
|
+
|
|
469
|
+
```json
|
|
470
|
+
{ "id": "agenda", "type": "booking", "position": { "x": 0, "y": 500 },
|
|
471
|
+
"data": { "label": "Escolha um horário", "event_type_id": "34", "required": false } }
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Ligue `success` (obrigatória) pro caminho de quem agendou e `no_booking` pro de quem seguiu sem
|
|
475
|
+
agendar (com `required: true` essa saída deixa de existir). O tipo de evento vem de
|
|
476
|
+
`lionchat_booking_event_types_list`.
|
|
477
|
+
|
|
478
|
+
## Armadilhas
|
|
479
|
+
|
|
480
|
+
- **O `slug` é EDITÁVEL** (mudou em 16/08). Trocar o slug derruba os links já divulgados —
|
|
481
|
+
inclusive os já enviados em mensagens. Vazio mantém o atual; duplicado dá 422.
|
|
482
|
+
- **`inbox_id` só troca por caixa do MESMO tipo de canal** (`channel type cannot change`), e a
|
|
483
|
+
troca só vale ao republicar.
|
|
484
|
+
- **`published_data` nunca sai pela API.** Para saber o que está no ar, olhe `published_at` e
|
|
485
|
+
`published_inbox_id`.
|
|
486
|
+
- **`form_data` no update SUBSTITUI o desenho inteiro.** Não há merge. Leia com `show`, altere e
|
|
487
|
+
mande o desenho completo de volta.
|
|
488
|
+
- **A cópia nasce desligada e não publicada** — e copia o `inbox_id`. Imagens viram blobs novos.
|
|
489
|
+
- **Publicar não ativa.** `publish` (foto) e `update` com `active: true` são dois passos.
|
|
490
|
+
- **Flag desligada = página 404.** Tools funcionando + link 404 = feature `lead_forms` desligada.
|
|
491
|
+
- **`media_urls` só existe no `show`.**
|
|
492
|
+
- **`answers` tem chaves sintéticas** `"<node_id>::<n>"` (bloco de Ações) — não são ids de bloco.
|
|
493
|
+
- **`embed_allowed` é veredito do servidor** (`check_embed`) — escrever `true` na mão faz a página
|
|
494
|
+
tentar um iframe que pode estar bloqueado.
|
|
495
|
+
- **Nunca escreva segredo em cabeçalho/corpo literal** de `api_request` — use
|
|
496
|
+
`{{conta.<variável>}}`, que resolve só no servidor.
|
|
497
|
+
- **Não há tool de rewind**: o "Voltar e corrigir" é da página pública (o lead corrige a própria
|
|
498
|
+
resposta; telefone corrigido reencaminha a mensagem pro número certo). Pela API de dashboard,
|
|
499
|
+
respostas são somente leitura.
|
|
@@ -70,6 +70,10 @@ Glossário completo de termos, status codes, enums e conceitos da plataforma. Us
|
|
|
70
70
|
- `private`: `true` = nota privada (só visível pra equipe, não pro cliente)
|
|
71
71
|
- `sender_type`: `User`, `Contact`, `Captain::Assistant`, `AgentBot`
|
|
72
72
|
- `processed_message_content`: texto após processamento (com markdown removido, links resolvidos)
|
|
73
|
+
- `content_attributes.forwarded`: `true` = a mensagem foi ENCAMINHADA pelo cliente (nos dois canais
|
|
74
|
+
de WhatsApp — oficial e QR Code). A bolha mostra o selo "Encaminhada"
|
|
75
|
+
- `content_attributes.frequently_forwarded`: `true` = encaminhada MUITAS vezes (o "encaminhada com
|
|
76
|
+
frequência" do WhatsApp). Útil pra responder "essa mensagem foi encaminhada?" sem chutar
|
|
73
77
|
|
|
74
78
|
## Attachment (Anexo)
|
|
75
79
|
|
|
@@ -93,6 +97,13 @@ Glossário completo de termos, status codes, enums e conceitos da plataforma. Us
|
|
|
93
97
|
- `file_size`: bytes
|
|
94
98
|
- `width`/`height`: pixels (imagens/vídeos)
|
|
95
99
|
|
|
100
|
+
### Tipo de arquivo não reprova mais (15/08/2026)
|
|
101
|
+
|
|
102
|
+
No anexo de **mensagem**, QUALQUER extensão é aceita — a validação é só de TAMANHO. Tipo sem teto
|
|
103
|
+
próprio cai no teto de "documento" (o maior). O `file_type` `file` cobre tudo que não é
|
|
104
|
+
imagem/áudio/vídeo. **Dois caminhos continuam com lista fechada de tipos permitidos**: o upload por
|
|
105
|
+
URL (o teste de mídia do FlowBuilder) e o chat interno da equipe — não confunda os três.
|
|
106
|
+
|
|
96
107
|
## Contact (Contato)
|
|
97
108
|
|
|
98
109
|
### Campos importantes
|
|
@@ -300,6 +311,12 @@ comportamento intencional.
|
|
|
300
311
|
- `start_time`, `end_time`: ISO 8601
|
|
301
312
|
- `status`: `scheduled`, `cancelled`, `completed`
|
|
302
313
|
|
|
314
|
+
**A IA agenda EXCLUSIVAMENTE por agenda de Booking (15/08/2026).** Não existe mais agendamento
|
|
315
|
+
avulso/direto pela IA — ela consulta as agendas liberadas, respeita os dias em que a agenda não
|
|
316
|
+
abre e só então cria a reserva. Depois de agendar, ela mesma entrega o link do Google Meet quando o
|
|
317
|
+
tipo de agendamento gera Meet (o link nasce alguns segundos depois; o sistema espera e volta pra
|
|
318
|
+
mandar). Detalhes em `lionchat://docs/best-practices`.
|
|
319
|
+
|
|
303
320
|
## Métricas (Reports)
|
|
304
321
|
|
|
305
322
|
### Endpoints `lionchat_reports_*` retornam métricas em SEGUNDOS por padrão
|
|
@@ -406,7 +423,7 @@ Campos MUTÁVEIS (atualizáveis livremente por IA/integração): `marital_status
|
|
|
406
423
|
|---|---|---|---|
|
|
407
424
|
| **LionCalls** | Voz pelo WhatsApp em caixa **QR Code**, mas pelo **motor próprio do LionChat** (credencial global, 1 sessão por caixa; flag `lioncalls_calling`, nasce desligada e é ligada conta a conta) | **NENHUMA — sem tool no MCP** | WhatsApp QR Code |
|
|
408
425
|
| **WhatsApp Calling (Cloud)** | Voz pelo WhatsApp na **API oficial** (enterprise) | `whatsapp_calls_*` + enable/disable_whatsapp_calling | WhatsApp oficial |
|
|
409
|
-
| **VoIP (Zenvia)** | Telefonia COMUM com softphone no navegador (ramais,
|
|
426
|
+
| **VoIP (Zenvia)** | Telefonia COMUM com softphone no navegador (ramais, ligações) | `voip_*` | Telefone |
|
|
410
427
|
| **VTCall** | Telefonia por **PABX click-to-call**, config por conta + ramal por atendente; **sem softphone no navegador** — o atendente fala pelo app/ramal do VTCall | `vtcall_settings_*` (show/update/test_connection), `vtcall_ramals_*` (list/create/destroy) | Telefone (PABX do cliente) |
|
|
411
428
|
|
|
412
429
|
Quando o usuário falar "ligação", descubra o canal ANTES de responder: caixa QR Code → **LionCalls**;
|
|
@@ -460,3 +477,34 @@ cards daquele funil).
|
|
|
460
477
|
A plataforma opera com 6 idiomas: `en`, `es`, `fr`, `it`, `pt`, `pt_BR` (default das contas
|
|
461
478
|
brasileiras: pt_BR). Conta com idioma antigo/descontinuado cai em inglês. Notas internas geradas
|
|
462
479
|
pela IA saem no idioma da conta.
|
|
480
|
+
|
|
481
|
+
## LeadForm (Formulário público de captação)
|
|
482
|
+
|
|
483
|
+
Formulário de captura próprio da conta, com página pública (link `/forms/<conta>/<slug>`), montado
|
|
484
|
+
num construtor de blocos. Feature liberada conta a conta.
|
|
485
|
+
|
|
486
|
+
### `LeadFormResponse.status`
|
|
487
|
+
| Valor | Significado |
|
|
488
|
+
|---|---|
|
|
489
|
+
| `in_progress` | O lead começou e ainda está preenchendo |
|
|
490
|
+
| `completed` | Chegou ao fim do formulário |
|
|
491
|
+
| `abandoned` | Parou no meio e a janela de abandono do formulário venceu |
|
|
492
|
+
|
|
493
|
+
O contato nasce em TRÊS momentos (mudou em 16/08): conclusão, fechamento do abandono, ou logo
|
|
494
|
+
antes de um bloco de Enviar WhatsApp. Resposta `abandoned` ganha o contato no fechamento do
|
|
495
|
+
abandono — com as respostas que a pessoa deu. Enquanto preenche, a lista de respostas mostra
|
|
496
|
+
`contact: {id: null, ..., pending: true}`.
|
|
497
|
+
|
|
498
|
+
Termos do formulário (18/08):
|
|
499
|
+
- **Caixa vinculada** — a caixa de WhatsApp do formulário (`inbox_id`); obrigatória pra publicar.
|
|
500
|
+
- **Link identificado** — link de formulário enviado numa conversa sai carimbado (`lt_form`) e o
|
|
501
|
+
formulário pula nome/telefone/e-mail já conhecidos, sem nunca exibir o dado.
|
|
502
|
+
- **Simulador** — `lead_forms_test_run`: roda o rascunho no motor real sem deixar rastro.
|
|
503
|
+
- **Página externa (`embed`)** — bloco que exibe um site de fora dentro do formulário (checkout,
|
|
504
|
+
simulador, agenda), com conferência prévia de "aceita iframe?" (`check_embed`).
|
|
505
|
+
- **Definir variável (`set_variable`)** — bloco invisível que cria `{{var.<nome>}}`.
|
|
506
|
+
- **Agendamento (`booking`)** — calendário dentro do formulário, ligado a um tipo de evento.
|
|
507
|
+
|
|
508
|
+
Detalhes completos (construtor, página pública, blocos, gatilhos de flow, tetos e limites) no
|
|
509
|
+
resource `lionchat://docs/formularios-publicos`.
|
|
510
|
+
|