Captar leads do site
Alguém preencheu o formulário de contato do seu site. Com uma chamada, esse contato entra no Faturei Hoje como lead e já abre uma tarefa para alguém da equipe retornar.
Como fica o desenho
Seção intitulada “Como fica o desenho”navegador do visitante -> seu servidor -> POST /public/v1/leadsO formulário manda para o seu servidor, e é o seu servidor que chama a API. A chave nunca vai para o navegador: qualquer um que abrir o código da página levaria a chave junto.
A API não tem proteção contra robô para o seu formulário. Filtro de spam, captcha e limite por visitante ficam do seu lado, antes da chamada.
A chave
Seção intitulada “A chave”Crie uma chave só para o site, com o mínimo:
| Permissão | Para quê |
|---|---|
leads:create | Cadastrar o lead |
tasks:create | Abrir a tarefa de contato. Sem ela, só dá para cadastrar com task: null |
team:read | Opcional. Só se você for escolher quem fica responsável pela tarefa |
O membro que a chave representa precisa ter acesso a clientes no painel e, quando a captação abre tarefa, também a tarefas. Veja Permissões.
A chamada
Seção intitulada “A chamada”POST /public/v1/leads recebe o nome e ao menos um contato, email ou phone. Sem nenhum dos dois, a resposta é 422.
# Gere a chave uma vez e guarde: numa nova tentativa, repita com o mesmo valor.LEAD_KEY=$(uuidgen)curl -X POST "https://api.fatureihoje.com/public/v1/leads" \ -H "Authorization: Bearer $FH_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $LEAD_KEY" \ -d '{ "name": "Ana Souza", "email": "ana@example.com", "phone": "+5511999990000", "source": "site", "notes": "Quero um orçamento de instalação de câmeras.", "task": { "due_in_hours": 4, "priority": "high" } }'import { randomUUID } from 'node:crypto';
// Em produção, isto vem do formulário que o seu servidor recebeu.const form = { name: 'Ana Souza', email: 'ana@example.com', phone: '+5511999990000', message: 'Quero um orçamento de instalação de câmeras.',};
// Uma chave por envio do formulário. Guarde junto com o envio para repetir igual.const idempotencyKey = randomUUID();
const res = await fetch('https://api.fatureihoje.com/public/v1/leads', { method: 'POST', headers: { Authorization: `Bearer ${process.env.FH_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey, }, body: JSON.stringify({ name: form.name, email: form.email, phone: form.phone, source: 'site', notes: form.message, task: { due_in_hours: 4, priority: 'high' }, }),});
const body = await res.json();
if (!res.ok) { throw new Error(`${res.status} ${body.error.code}: ${body.error.message}`);}
console.log(body.lead.id, body.deduplicated ? 'cadastro reaproveitado' : 'lead novo');<?php
// Em produção, isto vem do formulário que o seu servidor recebeu.$form = [ 'name' => 'Ana Souza', 'email' => 'ana@example.com', 'phone' => '+5511999990000', 'message' => 'Quero um orçamento de instalação de câmeras.',];
// Uma chave por envio do formulário. Guarde junto com o envio para repetir igual.$idempotencyKey = bin2hex(random_bytes(16));
$ch = curl_init('https://api.fatureihoje.com/public/v1/leads');curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('FH_API_KEY'), 'Content-Type: application/json', 'Idempotency-Key: ' . $idempotencyKey, ], CURLOPT_POSTFIELDS => json_encode([ 'name' => $form['name'], 'email' => $form['email'], 'phone' => $form['phone'], 'source' => 'site', 'notes' => $form['message'], 'task' => ['due_in_hours' => 4, 'priority' => 'high'], ]),]);
$body = json_decode(curl_exec($ch), true);$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 201) { throw new RuntimeException($status . ' ' . $body['error']['code'] . ': ' . $body['error']['message']);}
echo $body['lead']['id'], ' ', $body['deduplicated'] ? 'cadastro reaproveitado' : 'lead novo', PHP_EOL;import jsonimport osimport urllib.errorimport urllib.requestimport uuid
# Em produção, isto vem do formulário que o seu servidor recebeu.form = { "name": "Ana Souza", "email": "ana@example.com", "phone": "+5511999990000", "message": "Quero um orçamento de instalação de câmeras.",}
# Uma chave por envio do formulário. Guarde junto com o envio para repetir igual.idempotency_key = str(uuid.uuid4())
payload = { "name": form["name"], "email": form["email"], "phone": form["phone"], "source": "site", "notes": form["message"], "task": {"due_in_hours": 4, "priority": "high"},}
request = urllib.request.Request( "https://api.fatureihoje.com/public/v1/leads", data=json.dumps(payload).encode(), method="POST", headers={ "Authorization": f"Bearer {os.environ['FH_API_KEY']}", "Content-Type": "application/json", "Idempotency-Key": idempotency_key, },)
try: with urllib.request.urlopen(request) as response: body = json.load(response)except urllib.error.HTTPError as failure: error = json.load(failure)["error"] raise SystemExit(f"{failure.code} {error['code']}: {error['message']}")
print(body["lead"]["id"], "cadastro reaproveitado" if body["deduplicated"] else "lead novo")O que cada campo faz, com os tamanhos máximos, está na Referência. Os que valem comentário:
sourcediz de onde o lead veio. Use um valor por formulário ou campanha (site,landing-black-friday) e você consegue separar os leads depois.notesé a mensagem que a pessoa escreveu. Ela vai para as observações do cadastro.taskcontrola a tarefa de contato. Sem o campo, a tarefa padrão é aberta, com prazo de 24 horas. Com um objeto, você escolhe título, descrição, prazo (due_in_hoursoudue_at, um ou outro), prioridade e responsável. Comtask: null, nenhuma tarefa é aberta.
O que volta
Seção intitulada “O que volta”A resposta é 201 com um objeto lead_capture:
lead: o cadastro, com oid. Esse id é o mesmo do cliente e serve emGET /public/v1/clients/{id}.task: a tarefa de contato aberta, ounullquando você mandoutask: null.deduplicated:truequando o cadastro já existia.
Contato que já está na base
Seção intitulada “Contato que já está na base”Se o telefone ou o e-mail já pertence a um cadastro da empresa, a API reaproveita esse cadastro e não sobrescreve nada: nem nome, nem contato, nem a situação. Um cliente ativo que preenche o formulário de novo continua ativo. A tarefa de contato é aberta do mesmo jeito, porque alguém precisa retornar.
Quando isso acontece e a chave não tem leads:read nem clients:read, lead e task vêm só com object e id. É proposital: uma chave que só cadastra não lê o cadastro de quem já estava na base.
Escolher quem retorna
Seção intitulada “Escolher quem retorna”Sem responsável, a tarefa nasce sem dono. Para mandar para alguém, busque a pessoa em GET /public/v1/team e mande o id dela em task.assignee_id, junto com o type dela em task.assignee_type. Um sem o outro responde 422.
{ "name": "Ana Souza", "phone": "+5511999990000", "task": { "assignee_id": "3f6c2a9e-8b1d-4e5f-9a7c-2d4b6e8f0a1c", "assignee_type": "org_member" }}A lista da equipe muda pouco. Leia uma vez e guarde, em vez de consultar a cada lead.
Quando a chamada falha
Seção intitulada “Quando a chamada falha”Guarde o envio do formulário no seu lado antes de chamar a API. Se a chamada falhar por rede, tempo esgotado, 429 ou 5xx, tente de novo mais tarde com a mesma Idempotency-Key: o lead não é cadastrado duas vezes. Veja Idempotência.
| Resposta | O que fazer |
|---|---|
422 validation_failed | Corrija o campo apontado em errors. Repetir igual não resolve |
403 permission_missing | Falta permissão na chave, geralmente tasks:create |
403 member_permission_denied | O membro da chave não tem acesso a clientes no painel, ou a tarefas quando a captação abre tarefa |
429 rate_limit_exceeded | Espere o Retry-After e repita. Veja Limites de uso |
| 5xx ou falha de rede | Repita com a mesma Idempotency-Key |
O catálogo completo está em Erros.
Próximo passo
Seção intitulada “Próximo passo”- Sincronizar clientes com CRM ou ERP: levar o cadastro para o seu sistema.
- Webhooks: o evento
client.createdavisa o seu sistema quando entra um cadastro novo, venha ele do site, do painel ou do WhatsApp.