Captação de Leads
A captação de leads cria negócios no CRM a partir de um formulário no site do cliente, sem precisar de uma API Key. Cada formulário de captação é configurado no painel Maestron dentro de um funil, aponta para um estágio fixo e gera um token publicável (mcf_...) cuja única capacidade é criar um negócio naquele funil/estágio — ele não lê, não edita e não apaga nada.
Como configurar
Seção intitulada “Como configurar”No painel Maestron: CRM → abrir um funil → Configurar funil → aba Captação → Adicionar formulário. Ali você define:
- Etapa alvo, origem, status inicial, responsável e temperatura — resolvidos no servidor, o site nunca os escolhe.
- Mapa de campos: o
namede cada campo do formulário do site → o campo do lead (contact_name,client_email,client_phone,company_name,notes, ou um campo customizado do funil viacustom:<chave>). - Origens permitidas: lista de domínios que podem enviar. Vazio = qualquer origem.
- Anti-spam: janela de deduplicação por e-mail e nome do campo honeypot.
- Redirecionar para: URL de “obrigado” usada quando o envio vem de um
<form>HTML puro. - Resposta automática: e-mail de confirmação ao lead, sem marca Maestron, com assunto/corpo/
Reply-Toque você define. - Webhook de saída: uma URL
https://que recebe cada lead comoPOSTJSON assinado. - Pausar automaticamente: teto de envios/hora — acima dele o formulário é desativado e o responsável avisado. O painel também alerta quando um formulário com tráfego para de receber envios (tag removida do site).
O painel mostra métricas por formulário (envios, leads, spam, conversão) e o log dos últimos envios — os name dos campos recebidos, para depurar o mapeamento.
Gerenciar formulários exige a permissão crm.intake.manage no painel. Regenerar o token invalida o anterior na hora.
Todos os caminhos abaixo partem de https://api.maestron.com.br/api/v1. Nenhum exige header Authorization — a identidade vem do token na URL.
Enviar um lead
Seção intitulada “Enviar um lead”Enviar um lead
Cria um negócio no funil/estágio do formulário. Aceita JSON (integração / tag JS) ou form-urlencoded (formulário HTML puro).
Parâmetros de caminho
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
token | string | Sim | Token publicável do formulário (mcf_...). |
Corpo (body)
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
contact_name | string | — | Nome do contato. Máx. 200. Ausente, cai no e-mail ou telefone. |
client_email | string | — | E-mail do contato. Máx. 200. |
client_phone | string | — | Telefone do contato. Máx. 50. |
company_name | string | — | Empresa do contato. Máx. 200. |
notes | string | — | Observações. Máx. 5000. |
expected_close_date | string (data) | — | Previsão de fechamento, YYYY-MM-DD. Só aceito se mapeado no formulário. |
page_url | string | — | URL da página onde o formulário foi enviado. Guardado na auditoria, não no lead. |
referrer | string | — | Referrer do navegador. Guardado na auditoria. |
utm | object | — | Objeto com chaves de campanha (source, medium, campaign, term, content, gclid, fbclid). Guardado na auditoria. |
curl -X POST https://api.maestron.com.br/api/v1/crm/intake/{token} \
-H "Content-Type: application/json" \
-d '{
"contact_name": "Maria Silva",
"client_email": "maria@acme.com.br",
"client_phone": "+55 11 99999-0000",
"company_name": "Acme Ltda",
"page_url": "https://acme.com.br/contato",
"utm": {
"source": "google",
"medium": "cpc",
"campaign": "institucional"
}
}' <?php
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.maestron.com.br/api/v1/crm/intake/{token}', [
'headers' => ['Accept' => 'application/json'],
'json' => [
'contact_name' => 'Maria Silva',
'client_email' => 'maria@acme.com.br',
'client_phone' => '+55 11 99999-0000',
'company_name' => 'Acme Ltda',
'page_url' => 'https://acme.com.br/contato',
'utm' => [object Object],
],
]);
$data = json_decode($response->getBody(), true); import requests
headers = {}
payload = {
"contact_name": "Maria Silva",
"client_email": "maria@acme.com.br",
"client_phone": "+55 11 99999-0000",
"company_name": "Acme Ltda",
"page_url": "https://acme.com.br/contato",
"utm": [object Object],
}
response = requests.post(
"https://api.maestron.com.br/api/v1/crm/intake/{token}",
headers=headers,
json=payload,
)
data = response.json() const response = await fetch("https://api.maestron.com.br/api/v1/crm/intake/{token}", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"contact_name": "Maria Silva",
"client_email": "maria@acme.com.br",
"client_phone": "+55 11 99999-0000",
"company_name": "Acme Ltda",
"page_url": "https://acme.com.br/contato",
"utm": {
"source": "google",
"medium": "cpc",
"campaign": "institucional"
}
}),
});
const data = await response.json(); using var client = new HttpClient();
var payload = new StringContent(
"{\"contact_name\":\"Maria Silva\",\"client_email\":\"maria@acme.com.br\",\"client_phone\":\"+55 11 99999-0000\",\"company_name\":\"Acme Ltda\",\"page_url\":\"https://acme.com.br/contato\",\"utm\":{\"source\":\"google\",\"medium\":\"cpc\",\"campaign\":\"institucional\"}}",
System.Text.Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://api.maestron.com.br/api/v1/crm/intake/{token}", payload);
var data = await response.Content.ReadAsStringAsync(); POST /crm/intake/{token} HTTP/1.1
Host: api.maestron.com.br
Content-Type: application/json
{
"contact_name": "Maria Silva",
"client_email": "maria@acme.com.br",
"client_phone": "+55 11 99999-0000",
"company_name": "Acme Ltda",
"page_url": "https://acme.com.br/contato",
"utm": {
"source": "google",
"medium": "cpc",
"campaign": "institucional"
}
} Respostas
{
"message": "Lead recebido.",
"data": {
"id": "f1a2b3c4-5d6e-4f7a-8b9c-0d1e2f3a4b5c"
}
} {
"message": "Recebido.",
"data": null
} "Location: https://acme.com.br/obrigado" {
"message": "Os dados informados são inválidos.",
"data": null,
"errors": {
"contact_name": [
"Informe ao menos nome, e-mail ou telefone."
]
}
} {
"message": "Origem não autorizada.",
"data": null
} {
"message": "Recurso não encontrado.",
"data": null
} {
"message": "Muitos envios. Aguarde um momento antes de tentar novamente.",
"data": null
} Regras:
- Pelo menos um de
contact_name,client_emailouclient_phoneé obrigatório — senão422. - Campos fora do padrão (
telefone,empresa, …) são traduzidos pelo mapa de campos do formulário antes da validação. Campos com nome canônico não precisam de mapa. funnel_id,stage_id,status,source,assignee_idecontact_datenão são aceitos no corpo — vêm do formulário, resolvidos no servidor.- O corpo pode ser
application/jsonouapplication/x-www-form-urlencoded. ComAccept: text/htmle umredirect_urlconfigurado, a resposta é302para essa URL.
Mapa de campos
Seção intitulada “Mapa de campos”O corpo do lead usa nomes canônicos: contact_name, client_email, client_phone, company_name, notes. Um formulário de site raramente usa esses nomes — usa nome, seu-email, telefone-2, mensagem. O mapa de campos liga um ao outro.
A chave do mapa é o atributo name do campo no HTML do site — não o id, não o label, não o texto do placeholder. É o name porque é isso que o navegador envia no corpo da requisição (e o que o FormData lê na tag JS).
Exemplo — formulário no site:
<form> <input name="nome-completo" /> <input name="e-mail" type="email" /> <input name="fone" type="tel" /> <textarea name="obs"></textarea></form>Mapa configurado no painel (ou o efeito equivalente ao enviar já com os nomes canônicos):
name no site | Campo do lead |
|---|---|
nome-completo | contact_name |
e-mail | client_email |
fone | client_phone |
obs | notes |
Campos que podem ser alvo: contact_name, client_email, client_phone, company_name, notes, expected_close_date (data YYYY-MM-DD, ex.: de um <input type="date">) e qualquer campo customizado do funil no formato custom:<chave> (ex.: custom:numero_de_lojas) — os campos são definidos no painel em Configurar funil → Campos. O valor mapeado a um custom: entra estruturado em custom_fields do negócio (ver Negócios → Campos customizados), não nas observações. Na captação a coerção é tolerante: valor que não encaixa no tipo é descartado, nunca gera 422.
Regras:
- Campos que já usam o nome canônico não precisam de entrada no mapa — passam direto.
- A tradução acontece antes da validação:
422por “sem canal de contato” só ocorre se, depois de aplicar o mapa, ainda não hácontact_name,client_emailnemclient_phone. - Se
contact_nameficar vazio mas houver e-mail ou telefone, o negócio é criado com ocontact_namepreenchido a partir do e-mail (ou, na falta dele, do telefone) — o CRM nunca fica com um card sem rótulo. - Campos não mapeados não se perdem. Qualquer outro campo do site (
cargo,faturamento,como nos conheceu…) é anexado às observações do negócio num bloco— Enviado pelo formulário —, uma linha por campo. Limite: 20 campos, 500 caracteres cada. O contexto de campanha (page_url,referrer,utm) tem tratamento próprio (vai para a auditoria, não para as observações).
A tag intake.js ainda tem uma autodetecção que dispensa o mapa nos casos óbvios (campo type="email" → client_email, type="tel" → client_phone, name contendo nome/empresa, <textarea> → notes). A precedência é: data-maestron-field="..." no input → mapa de campos → autodetecção.
Deduplicação e honeypot
Seção intitulada “Deduplicação e honeypot”Duas proteções configuradas por formulário, ambas retornando 202 quando disparam — o cliente (ou o bot) não distingue de um envio aceito.
Janela anti-duplicidade (dedupe_minutes, padrão 10): se um lead com o mesmo client_email já entrou no mesmo funil dentro dos últimos N minutos, o envio retorna 202 e nenhum negócio novo é criado — o contador de envios do formulário também não sobe. Cobre o visitante que clica “Enviar” duas vezes, recarrega a página de agradecimento ou volta e reenvia, e o bot que repete o mesmo payload. 0 desativa a janela. Envios sem client_email nunca são deduplicados.
Honeypot (honeypot_field, padrão mkt__hp): um campo de texto escondido. A tag intake.js injeta esse campo em cada formulário; um humano nunca o vê nem o preenche, um bot que preenche todos os campos, sim. Se o campo vier preenchido, a resposta é 202 e o envio é descartado em silêncio. Em um formulário HTML montado à mão, adicione você o campo escondido com esse name para ter a mesma proteção.
Configuração do formulário (para a tag JS)
Seção intitulada “Configuração do formulário (para a tag JS)”Ler a configuração do formulário
Consumido pela tag intake.js para se autoconfigurar. Não expõe origens permitidas, responsável nem contadores.
Parâmetros de caminho
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
token | string | Sim | Token publicável do formulário (mcf_...). |
curl -X GET https://api.maestron.com.br/api/v1/crm/intake/{token}/config <?php
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.maestron.com.br/api/v1/crm/intake/{token}/config', [
'headers' => ['Accept' => 'application/json'],
]);
$data = json_decode($response->getBody(), true); import requests
headers = {}
response = requests.get(
"https://api.maestron.com.br/api/v1/crm/intake/{token}/config",
headers=headers,
)
data = response.json() const response = await fetch("https://api.maestron.com.br/api/v1/crm/intake/{token}/config", {
method: "GET",
headers: {
},
});
const data = await response.json(); using var client = new HttpClient();
var response = await client.GetAsync("https://api.maestron.com.br/api/v1/crm/intake/{token}/config");
var data = await response.Content.ReadAsStringAsync(); GET /crm/intake/{token}/config HTTP/1.1
Host: api.maestron.com.br Respostas
{
"message": "Configuração do formulário.",
"data": {
"selector": "form",
"field_map": {
"telefone": "client_phone",
"empresa": "company_name"
},
"honeypot_field": "mkt__hp",
"capture_utm": true,
"success_message": "Recebemos seu contato!"
}
} {
"message": "Recurso não encontrado.",
"data": null
} A tag intake.js
Seção intitulada “A tag intake.js”Baixar a tag de captação
Script estático versionado na URL (estilo js.stripe.com/v3). Cacheável — o CDN fica na frente. Versão atual: 1.0.
Parâmetros de caminho
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
version | string | Sim | Versão do script no formato N.N (ex.: 1.0). Versão desconhecida retorna 404. |
curl -X GET https://api.maestron.com.br/api/v1/crm/intake/{version}/intake.js <?php
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.maestron.com.br/api/v1/crm/intake/{version}/intake.js', [
'headers' => ['Accept' => 'application/json'],
]);
$data = json_decode($response->getBody(), true); import requests
headers = {}
response = requests.get(
"https://api.maestron.com.br/api/v1/crm/intake/{version}/intake.js",
headers=headers,
)
data = response.json() const response = await fetch("https://api.maestron.com.br/api/v1/crm/intake/{version}/intake.js", {
method: "GET",
headers: {
},
});
const data = await response.json(); using var client = new HttpClient();
var response = await client.GetAsync("https://api.maestron.com.br/api/v1/crm/intake/{version}/intake.js");
var data = await response.Content.ReadAsStringAsync(); GET /crm/intake/{version}/intake.js HTTP/1.1
Host: api.maestron.com.br Respostas
"// JavaScript — Content-Type: application/javascript; Cache-Control: public, max-age=3600" "" Cole no <head> do site (funciona como Custom HTML tag no Google Tag Manager):
<script async src="https://api.maestron.com.br/api/v1/crm/intake/1.0/intake.js" data-maestron-token="mcf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"></script>O script:
- Lê a configuração em
/crm/intake/{token}/config— o mapa de campos é definido no painel, o site não precisa mudar quando o mapeamento muda. - Encontra os
<form>da página (ignora os que têmdata-maestron-ignore) e injeta o campo honeypot. - No
submit, monta o payload a partir dos campos (mapa + autodetecção deemail/tel/nome/empresa, oudata-maestron-field="contact_name"no input), anexapage_url,referrere UTMs, e fazPOSTpara/crm/intake/{token}comkeepalive— sem bloquear o envio nativo do formulário. - Dispara o evento
maestron:leadnowindow(detail: { ok, status, leadId, message }) para o site engatar analytics ou uma tela de obrigado.
Para bloquear o envio nativo e aguardar a resposta, adicione data-maestron-block ao <form>.
API para SPA (React, Vue, Angular…)
Seção intitulada “API para SPA (React, Vue, Angular…)”Quando a página não tem um <form> nativo, use maestron('submit', payload) — devolve uma Promise que nunca rejeita e resolve com { ok, status, leadId, errors }. Ele já anexa page_url, referrer e UTMs.
// Reactimport { useState } from 'react';
function ContactForm() { const [sent, setSent] = useState(false);
async function onSubmit(e) { e.preventDefault(); const form = new FormData(e.currentTarget); const res = await window.maestron('submit', { contact_name: form.get('name'), client_email: form.get('email'), notes: form.get('message'), }); if (res.ok) setSent(true); }
return sent ? <p>Recebemos seu contato!</p> : ( <form onSubmit={onSubmit}> <input name="name" required /> <input name="email" type="email" /> <textarea name="message" /> <button>Enviar</button> </form> );}<!-- Vue 3 --><script setup>import { ref } from 'vue'const sent = ref(false)async function submit(e) { const f = new FormData(e.target) const res = await window.maestron('submit', { contact_name: f.get('name'), client_email: f.get('email'), }) if (res.ok) sent.value = true}</script>
<template> <p v-if="sent">Recebemos seu contato!</p> <form v-else @submit.prevent="submit"> <input name="name" required /> <input name="email" type="email" /> <button>Enviar</button> </form></template>maestron('track', payload) continua existindo para o caso “envia e esquece” (sem aguardar a resposta).
Formulário HTML puro (sem JavaScript)
Seção intitulada “Formulário HTML puro (sem JavaScript)”<form action="https://api.maestron.com.br/api/v1/crm/intake/mcf_xxxxxxxx" method="POST"> <input type="text" name="contact_name" placeholder="Seu nome" /> <input type="email" name="client_email" placeholder="Seu e-mail" /> <input type="tel" name="client_phone" placeholder="Seu telefone" /> <button type="submit">Enviar</button></form>Com um redirect_url configurado no formulário, o navegador é redirecionado para lá após o envio (só http(s) — javascript:/data: são recusados). Sem redirect_url, a resposta é uma página mínima de confirmação, sem marca.
Página de formulário hospedada
Seção intitulada “Página de formulário hospedada”Se o cliente não quer construir HTML, o Maestron hospeda o formulário. A página é sem marca (só o nome da empresa), responsiva, com honeypot embutido.
- Link direto:
https://api.maestron.com.br/api/v1/crm/intake/{token}/form— dá pra compartilhar como está. Aceita?title=Fale%20conoscoe?accent=16a34a(hex de 6 dígitos). - Embed:
<iframe src="https://api.maestron.com.br/api/v1/crm/intake/mcf_xxxxxxxx/form" title="Formulário de contato" style="width:100%;max-width:480px;height:560px;border:0;" loading="lazy"></iframe>Os campos vêm do mapa de campos do formulário (mais nome/e-mail/telefone sempre). O envio usa o mesmo endpoint e respeita o redirect_url.
Resposta automática ao lead
Seção intitulada “Resposta automática ao lead”Se auto_reply estiver ligado no formulário e o envio trouxer client_email, o lead recebe um e-mail de confirmação sem marca Maestron — o nome do remetente é a empresa do cliente e o corpo é o texto configurado (auto_reply_subject / auto_reply_body). O Reply-To aponta para reply_to_email (ou, na falta, para o responsável / dono da empresa), então quando o lead responde a mensagem cai no time do cliente.
Atribuição de campanha
Seção intitulada “Atribuição de campanha”Quando o envio traz contexto de campanha (utm.*, gclid, fbclid, page_url, referrer), o negócio criado carrega um objeto attribution:
{ "source": "google", "medium": "cpc", "campaign": "institucional", "gclid": "abc123", "landing_page": "https://cliente.com.br/lp/x", "referrer": "https://www.google.com/", "via": "intake_form"}A tag intake.js preenche isso sozinha. attribution aparece no GET /v1/crm/deals/{id} e no corpo do webhook.
Webhook de saída
Seção intitulada “Webhook de saída”Cada formulário pode ter uma URL de webhook (https://). A cada lead capturado, o Maestron faz um POST JSON assinado para ela — sem passar por Zapier.
{ "event": "lead.captured", "occurred_at": "2026-09-08T12:00:00-03:00", "form": { "id": "…", "name": "LP Black Friday" }, "lead": { "id": "…", "funnel_id": "…", "stage_id": "…", "contact_name": "Maria Silva", "company_name": "Acme", "client_email": "maria@acme.com.br", "client_phone": "+55 11 99999-0000", "source": "site", "status": "em_contato", "notes": "…", "attribution": { "source": "google", "via": "intake_form" }, "created_at": "2026-09-08T12:00:00-03:00" }}Headers: X-Maestron-Event, X-Maestron-Delivery (id único da entrega), e X-Maestron-Signature: sha256=<hmac> — HMAC-SHA256 do corpo bruto com o segredo do webhook (gerado no painel junto com a URL, prefixo whsec_). Verifique antes de confiar no payload:
$expected = 'sha256='.hash_hmac('sha256', file_get_contents('php://input'), $secret);if (! hash_equals($expected, $_SERVER['HTTP_X_MAESTRON_SIGNATURE'] ?? '')) { http_response_code(401); exit; }Entrega: responda 2xx para confirmar. 4xx (exceto 429) = erro definitivo, sem retry. 5xx / 429 / timeout (5s) = reentrega com backoff exponencial (até 6 tentativas em 24h). O painel tem um botão de teste que dispara um event: "ping".
Segurança e limites
Seção intitulada “Segurança e limites”- Rate limit: 20 envios/minuto por token e 10/minuto por IP. Ao estourar,
429comRetry-After. - Origens: se o formulário tem lista de origens permitidas, um
Originfora dela recebe403— validado no servidor, independente do CORS do navegador. O painel recusa domínios conhecidos (google, facebook, youtube, o própriomaestron.com.br…) nessa lista — quase sempre erro de digitação; use o domínio do site do cliente. - CORS: o endpoint responde a qualquer origem (
Access-Control-Allow-Originreflete oOrigin), sem credenciais — a leitura da resposta pelo navegador funciona de qualquer site. - Token: revogável e regenerável a qualquer momento no painel; o token antigo para de responder na hora.
- Erro interno nunca vaza detalhe para o cliente — apenas o envelope genérico.