Quando o paciente chama no atendimento, o seu sistema pode perguntar ao Clube do Lab de onde ele veio: o anúncio, a campanha, a página que ele estava vendo e a cidade. O atendente vê a origem antes mesmo de existir orçamento.
A mesma informação também volta sozinha na resposta de cada envio de orçamento, sem chamada extra.
Endpoint#
POST /v1/integrations/attribution/lookup{
"phone": "+55 47 99999-0000",
"email": "[email protected]",
"contactExternalId": "pac_88",
"at": "2026-10-07T13:00:00-03:00"
}| Campo | Obrigatório | Regra |
|---|---|---|
phone | um dos três | Qualquer formato, com ou sem +55, máscara ou nono dígito. |
email | um dos três | E-mail do paciente. |
contactExternalId | um dos três | Id do paciente no seu sistema (o mesmo dos orçamentos). |
at | não | ISO 8601 com fuso. Padrão: agora. |
A consulta é POST, e não GET, para que telefone e e-mail não vão para a URL, que fica registrada em log de proxy.
Como escolhemos o lead#
A resposta é o último toque até at: o lead mais recente da pessoa criado até aquele momento (com 1 hora de folga), nos últimos 180 dias. É a mesma regra que liga um orçamento à origem, então consulta e orçamento sempre apontam para o mesmo lead.
Resposta#
{
"found": true,
"matchedBy": "phone",
"attribution": {
"leadId": "66f1c0ffee",
"capturedAt": "2026-10-01T13:00:00.000Z",
"channel": { "key": "meta-ads", "label": "Meta Ads", "paid": true },
"sourceType": "form",
"utm": {
"source": "instagram",
"medium": "cpc",
"campaign": "checkup-outubro",
"term": null,
"content": null
},
"page": { "title": "Check-up completo", "path": "/check-up", "site": null },
"city": "Joinville",
"state": "SC",
"click": { "gclid": "Cj0K…", "fbclid": "IwAR…", "fbc": "fb.1.1696…", "fbp": "fb.1.1696…" }
}
}Sem lead da pessoa: { "found": false }.
| Campo | O que é |
|---|---|
matchedBy | Como achamos: phone, email, contact (orçamentos anteriores do mesmo paciente) ou created (lead criado pela própria integração). |
capturedAt | Quando o lead chegou ao Clube do Lab. |
channel | key é o canal normalizado (meta-ads, meta, google-ads, google, organico, whatsapp, email, bio, direct, integracao, manual ou o domínio de origem). label é o nome para exibir. paid indica anúncio. |
sourceType | Como chegou: form (formulário), whatsapp (clique no WhatsApp) ou phone. |
utm | Parâmetros da campanha. null quando ausentes. |
page | Página onde converteu: título, caminho e, quando a captura foi no site do laboratório, o domínio. |
city, state | Cidade e UF estimadas pelo acesso. |
click | Identificadores de clique de anúncio, só os presentes. Sem nenhum, o bloco não vem. |
O que não sai: o texto que o paciente escreveu no formulário e dados técnicos internos. Nome, telefone e e-mail também não, porque quem consulta já tem.
Dados de clique#
Com os identificadores de clique, o seu sistema pode avisar as plataformas de anúncio quando a venda fecha, e a campanha passa a otimizar por venda, não só por contato:
gclid: conversão offline no Google Ads.fbcefbp: evento de compra na Meta (Conversions API).
Atenção
Ao enviar a venda para a Meta, não inclua nome de exame nem caminho de página. A Meta classifica a fonte como saúde restrita e passa a descartar os eventos do laboratório.
Exemplos#
curl -X POST "https://api-dev.clubelab.com/v1/integrations/attribution/lookup" \
-H "Authorization: Bearer $CLAB_KEY" \
-H "Content-Type: application/json" \
-d '{ "phone": "47999990000" }'// Node.js 18+
const res = await fetch(
"https://api-dev.clubelab.com/v1/integrations/attribution/lookup",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLAB_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ phone: "47999990000" }),
},
);
const origem = await res.json();
if (origem.found) {
console.log(origem.attribution.channel.label, origem.attribution.utm.campaign);
}import os
import requests
res = requests.post(
"https://api-dev.clubelab.com/v1/integrations/attribution/lookup",
headers={"Authorization": f"Bearer {os.environ['CLAB_KEY']}"},
json={"phone": "47999990000"},
timeout=10,
)
origem = res.json()
if origem["found"]:
print(origem["attribution"]["channel"]["label"], origem["attribution"]["utm"]["campaign"])<?php
$ch = curl_init('https://api-dev.clubelab.com/v1/integrations/attribution/lookup');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CLAB_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['phone' => '47999990000']),
]);
$origem = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($origem['found']) {
echo $origem['attribution']['channel']['label'];
}Erros e limite#
| HTTP | Quando |
|---|---|
400 | identifier_required (nenhum dos três campos), ou campo inválido (at_invalid, phone_too_long…). |
401 | Chave ausente, inválida, revogada ou expirada. |
429 | Mais de 120 consultas por minuto por integração. Esse limite é separado do envio de orçamentos. Espere o Retry-After e tente de novo. |