Leads (Oportunidades)
Enviar leads para o funil comercial via API ou webhooks de entrada, com rastreabilidade de origem, canal, mídia e campanha.
Leads (Oportunidades)
Envie leads para o funil comercial do OctaBuild por dois caminhos:
| Caminho | Autenticação | Uso típico |
|---|---|---|
POST /v1/oportunidades | API Key (Authorization: Bearer) | Integrações servidor-a-servidor |
POST /api/webhooks/inbound/{token} | Token na URL + segredo opcional | Typebot, Zapier, n8n, landing pages, parceiros |
Ambos criam a pessoa (lead), a oportunidade, o touchpoint de conversão e disparam a distribuição automática (roleta) numa única transação — a menos que a requisição peça o contrário (ver Distribuição abaixo).
O corpo da requisição segue um contrato fixo — as mesmas chaves para todas as fontes e todos os tenants, no nível raiz do JSON. Não há mapeamento de campos por webhook: a fonte deve enviar os nomes de campo documentados aqui.
Rastreabilidade
Todo lead carrega uma hierarquia de atribuição:
| Nível | O que responde | Comportamento na API |
|---|---|---|
| Origem | Quem cadastrou (Corretor, Marketing, SDR, Correspondente, Gestor, Imobiliária Parceira) | Nunca criada pela API. No webhook, vem do cadastro da fonte; na API v1, o campo origin aceita um slug existente — inválido/ausente cai em marketing. |
| Canal | Onde foi captado (Instagram, Google, Site, OLX…) | Nunca criado pela API. Resolvido por slug ou nome contra o catálogo do painel; desconhecido → fica vazio (o texto bruto é preservado no touchpoint). |
| Mídia | Como foi captado (Mídia Paga, Mídia Orgânica) | Nunca criada pela API. Mesma resolução do canal. |
| Campanha → Conjunto → Anúncio | Qual campanha/conjunto/anúncio trouxe o lead | Criados automaticamente por nome (get-or-create). ad_id externo (Meta/Google) deduplica o anúncio. |
Os catálogos de Origem, Canal e Mídia são gerenciados em Configurações → Comercial → Origens & Canais.
Distribuição
Por padrão todo lead novo entra nas roletas ativas, que sorteiam o responsável. Dois campos, aceitos pelos dois caminhos, mudam esse comportamento:
| Campo | Tipo | Padrão | Efeito |
|---|---|---|---|
distribuir | boolean | true | false mantém o lead fora de toda roleta — ele é criado sem responsável e fica disponível para atribuição manual |
sdr_id | uuid | — | Atribui o lead direto a um usuário da organização, preenchendo SDR e responsável |
Enviar sdr_id já dispensa a roleta: a atribuição explícita substitui o sorteio,
mesmo com distribuir: true. Sem isso a roleta consumiria a vaga de um
participante sem conseguir atribuir o lead.
O sdr_id é o id do usuário (o mesmo do painel), e ele precisa ser membro da organização — caso contrário a requisição é recusada com 400 e nada é gravado. Leads com distribuir: false e sem sdr_id ficam sem responsável até alguém assumi-los no painel.
POST /v1/oportunidades
Cria (ou detecta duplicidade de) uma oportunidade. Requer API Key.
Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
nome | string | Sim | Nome do lead |
telefone | string | Não | Telefone (normalizado para E.164 BR) |
email | string | Não | E-mail válido |
empreendimento_id | uuid | Não | Empreendimento de interesse |
unidade_id | uuid | Não | Unidade de interesse |
observacoes | string | Não | Texto livre |
documento | string | Não | CPF (somente dígitos ou formatado) |
data_nascimento | string | Não | Data YYYY-MM-DD |
estado_civil | string | Não | Estado civil (ex.: casado) |
profissao | string | Não | Profissão |
renda_mensal | number | Não | Renda mensal |
tem_fgts | boolean | Não | Possui FGTS |
tem_dependente | boolean | Não | Possui dependentes |
rg | string | Não | RG |
cep | string | Não | CEP do endereço |
logradouro | string | Não | Logradouro |
numero | string | Não | Número |
complemento | string | Não | Complemento |
bairro | string | Não | Bairro |
cidade | string | Não | Cidade |
uf | string | Não | UF (2 letras) |
conjuge | objeto | Não | Dados do cônjuge (leads casados) — ver abaixo |
utm_source | string | Não | UTM de origem |
utm_medium | string | Não | UTM de mídia |
utm_campaign | string | Não | UTM de campanha |
utm_content | string | Não | UTM de conteúdo |
utm_term | string | Não | UTM de termo |
ad_id | string | Não | Id externo do anúncio (Meta/Google) — deduplica o anúncio na árvore |
form_id | string | Não | Id externo do formulário |
referrer_url | string | Não | URL de referência |
landing_url | string | Não | URL da landing page |
origin | string | Não | Slug de uma Origem existente (ex.: marketing). Inválido → marketing |
channel | string | Não | Slug ou nome de um Canal existente (ex.: instagram). Fallback: utm_source |
media_type | string | Não | Slug ou nome de uma Mídia existente (ex.: midia_paga) |
campaign_name | string | Não | Nome da campanha (get-or-create). Fallback: utm_campaign |
ad_set_name | string | Não | Nome do conjunto de anúncios (get-or-create, dentro da campanha) |
ad_name | string | Não | Nome do anúncio (get-or-create) |
capture_point | string | Não | Nome de um Ponto de Captação existente (nunca criado pela API) |
capture_location | string | Não | Local de captação em texto livre (endereço) |
capture_lat | number | Não | Latitude (-90 a 90) |
capture_lng | number | Não | Longitude (-180 a 180) |
distribuir | boolean | Não | Envia o lead para as roletas ativas. Padrão true; false = sem roleta |
sdr_id | uuid | Não | Usuário atribuído como SDR/responsável — dispensa a roleta |
Objeto conjuge
Quando o lead é casado, envie os dados do cônjuge — o OctaBuild cria (ou reusa,
por CPF e depois telefone) o cadastro da pessoa e a vincula à oportunidade como
conjuge:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
conjuge.nome | string | Sim (dentro do objeto) | Nome do cônjuge |
conjuge.telefone | string | Não | Telefone |
conjuge.email | string | Não | |
conjuge.documento | string | Não | CPF |
conjuge.rg | string | Não | RG |
conjuge.data_nascimento | string | Não | Data YYYY-MM-DD |
conjuge.profissao | string | Não | Profissão |
Request
curl -X POST "https://api.octabuild.ai/v1/oportunidades" \
-H "Authorization: Bearer ea_live_sua_chave_aqui" \
-H "Content-Type: application/json" \
-d '{
"nome": "Maria Silva",
"telefone": "+5561999990000",
"email": "maria@email.com",
"channel": "instagram",
"media_type": "midia_paga",
"campaign_name": "Lançamento Jardins",
"ad_set_name": "Publico frio DF",
"ad_name": "Video 30s",
"utm_source": "instagram",
"utm_medium": "paid_social",
"utm_campaign": "lancamento-jardins",
"documento": "123.456.789-00",
"estado_civil": "casado",
"renda_mensal": 8500,
"cep": "71900-000",
"logradouro": "Rua das Palmeiras",
"numero": "120",
"bairro": "Águas Claras",
"cidade": "Brasília",
"uf": "DF",
"conjuge": {
"nome": "João Silva",
"telefone": "+5561988880000",
"documento": "987.654.321-00"
}
}'Response — 201 Created (ou 200 se já existia em modo merge)
{
"success": true,
"data": {
"oportunidade_id": "550e8400-e29b-41d4-a716-446655440000",
"numero": 42,
"created": true
}
}Response — 409 Conflict (duplicado)
Telefone, e-mail ou CPF já usados em oportunidade ativa bloqueiam o cadastro (nada é gravado):
{
"success": false,
"error": {
"code": "DUPLICATE_TELEFONE",
"message": "Já existe uma oportunidade ativa com este telefone (#42)."
}
}Códigos possíveis: DUPLICATE_TELEFONE, DUPLICATE_EMAIL, DUPLICATE_CPF, VALIDATION_ERROR (400), RATE_LIMITED (429).
POST /api/webhooks/inbound/{token}
Webhook de entrada para fontes low-code (Typebot, Zapier, n8n, landing pages). Crie a fonte em Configurações → Integrações → Webhooks — a URL com o token é exibida na criação.
- Origem e Canal dos leads desta fonte são definidos no cadastro do webhook (não no corpo).
- Se a fonte tiver segredo, envie o header
x-webhook-secret. - Rate limit: 120 requisições/minuto por fonte.
Body (contrato fixo)
Além dos campos de atribuição (utm_*, ad_id, form_id, referrer_url, landing_url, campaign_name, ad_set_name, ad_name — mesmos significados da API v1), o webhook aceita campos de qualificação:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
nome | string | Um entre nome/telefone/email | Nome do lead |
telefone | string | ↑ | Telefone |
email | string | ↑ | |
documento | string | Não | CPF (somente dígitos ou formatado) |
data_nascimento | string | Não | Data YYYY-MM-DD |
estado_civil | string | Não | Estado civil |
profissao | string | Não | Profissão |
renda_mensal | string/number | Não | Renda mensal |
tem_fgts | string/boolean | Não | Aceita sim/não, true/false, 1/0 |
tem_dependente | string/boolean | Não | Idem |
rg | string | Não | RG |
cep | string | Não | CEP do endereço |
logradouro | string | Não | Logradouro |
numero | string | Não | Número |
complemento | string | Não | Complemento |
bairro | string | Não | Bairro |
cidade | string | Não | Cidade |
uf | string | Não | UF (2 letras) |
empreendimento_id | uuid | Não | Empreendimento de interesse |
unidade_id | uuid | Não | Unidade de interesse |
observacoes | string | Não | Texto livre |
conjuge_nome | string | Não | Nome do cônjuge — presente, ativa o vínculo do cônjuge |
conjuge_telefone | string | Não | Telefone do cônjuge |
conjuge_email | string | Não | E-mail do cônjuge |
conjuge_documento | string | Não | CPF do cônjuge |
conjuge_rg | string | Não | RG do cônjuge |
conjuge_data_nascimento | string | Não | Data YYYY-MM-DD |
conjuge_profissao | string | Não | Profissão do cônjuge |
distribuir | string/boolean | Não | Envia o lead para as roletas ativas. Padrão true; aceita sim/não, true/false, 1/0 |
sdr_id | uuid | Não | Usuário atribuído como SDR/responsável — dispensa a roleta |
No webhook os dados do cônjuge são chaves planas com prefixo conjuge_
(o contrato é raso). Na API v1, use o objeto aninhado conjuge.
O corpo inteiro da requisição é arquivado no lead para auditoria (payload do touchpoint), então campos extras não são perdidos — apenas não são estruturados.
Request
curl -X POST "https://app.octabuild.ai/api/webhooks/inbound/wh_seu_token_aqui" \
-H "Content-Type: application/json" \
-H "x-webhook-secret: whsec_seu_segredo" \
-d '{
"nome": "Maria Silva",
"telefone": "+5561999990000",
"email": "maria@email.com",
"utm_source": "instagram",
"utm_campaign": "lancamento-jardins",
"campaign_name": "Lançamento Jardins"
}'Response
201 (criado) / 200 (fundido) com { success, data: { oportunidade_id, numero, created } }, ou:
| Status | Motivo |
|---|---|
400 | JSON inválido, payload sem nome/telefone/email, ou sdr_id que não é usuário da organização |
401 | Segredo inválido |
404 | Token inexistente ou fonte inativa |
409 | Lead duplicado (error, duplicate_field, oportunidade_numero) |
429 | Rate limit excedido |