Cadastro
Os webhooks são cadastrados pelo próprio anunciante, no painel da nonStop: Configurações → Webhooks (usenonstop.com/configuracoes?tab=webhook ). A aba aparece para contas com plano ativo.
São duas listas:
| Lista | Eventos |
|---|---|
| Webhooks de imóveis | publication:create, publication:delete, property:update e property:delete |
| Webhooks de leads | lead:create |
Para cada endpoint são dois campos:
- Url: endereço completo, com
https://. Endereços internos (localhost, redes privadas, IP de metadados de nuvem) são recusados ao salvar. - Token: opcional, mas recomendado. Veja Autenticação abaixo.
Dá para cadastrar mais de um endpoint por lista. Não dá para escolher eventos: cada
endpoint recebe todos os da sua lista, e quem separa é o campo event do corpo.
Requisição
Cada evento é um POST com o corpo em JSON. Os formatos estão em
Imóvel e Lead.
POST /webhooks/nonstop HTTP/1.1
Content-Type: application/json
Authorization: Bearer <token cadastrado>
{"event":"property:update","property":{"id":"6650f1c2a8b4e93d7c1a2b3c", ...}}Autenticação
Com o campo Token preenchido, todo envio leva o header
Authorization: Bearer <token>. Sem token, o envio sai sem o header.
- O token é seu. É um segredo que você inventa e cadastra junto da URL, e não
tem relação com o token da API da nonStop. Use um valor longo e aleatório, como a
saída de
openssl rand -hex 32. - Não há assinatura (HMAC). A autenticação é só esse header: responda
401ao que chegar sem ele ou com outro valor, e usehttpspara que ele não trafegue aberto. - Não há IP fixo. Os envios saem de infraestrutura de nuvem sem faixa de IP publicada, então liberar por IP não funciona. Valide pelo token.
import { timingSafeEqual } from "node:crypto";
const expected = Buffer.from(`Bearer ${process.env.NONSTOP_WEBHOOK_TOKEN}`);
export async function POST(request: Request) {
const received = Buffer.from(request.headers.get("authorization") ?? "");
const authorized =
received.length === expected.length && timingSafeEqual(received, expected);
if (!authorized) return new Response(null, { status: 401 });
const body = await request.json();
switch (body.event) {
case "publication:create":
case "property:update":
// grava ou atualiza body.property, com body.property.id como chave
break;
case "publication:delete":
case "property:delete":
// tira do ar body.propertyId
break;
case "lead:create":
// cria body.lead no seu CRM, com body.lead.id como chave
break;
}
return new Response(null, { status: 204 });
}Entrega
- Qualquer resposta
2xxem até 5 segundos conta como entregue. O corpo da resposta é ignorado. Se o processamento for demorado, enfileire do seu lado e responda antes. - Redirecionamentos são seguidos até 3 vezes, repetindo o mesmo
POSTcom os mesmos headers, inclusive oAuthorization. Cadastre a URL final. - Falhou (status fora de
2xx, timeout ou erro de conexão)? O evento entra numa fila e é reenviado a cada 5 minutos, até 3 vezes. Depois disso, é descartado.
Repetição e ordem
O mesmo evento pode chegar mais de uma vez: basta você processar e responder depois dos 5 segundos. E um reenvio pode chegar depois de um evento mais novo do mesmo imóvel, porque ele leva o conteúdo do momento em que o evento nasceu, não o atual.
- Processe de forma idempotente, com
property.id/propertyId(imóveis) elead.id(leads) como chave. - Num
property:update, compareproperty.updatedAtcom o que você já tem e descarte o mais antigo.
Falhas seguidas
Cada falha conta contra o endpoint, e uma entrega bem-sucedida zera a contagem.
- 1ª e 2ª falha: o e-mail da conta recebe um aviso com o endpoint e o erro.
- 3ª falha: o endpoint é removido da lista, e o e-mail da conta recebe o aviso da remoção. A partir daí, nada mais é enviado para ele.
Depois de uma falha, as que vierem nos 10 minutos seguintes não contam: um lote de eventos que falha junto porque o seu servidor caiu gasta um aviso, não os três. Ou seja, para ser removido, o endpoint precisa falhar em três momentos diferentes, com pelo menos 10 minutos entre eles e nenhuma entrega bem-sucedida no meio.
Para voltar a receber, cadastre o endpoint de novo: salvar zera a contagem. Os eventos do período em que ele esteve fora não são reenviados. Para recuperar o estado, use a API de imóveis.
Quem recebe cada evento
publication:create/publication:delete: a conta em cujo próprio site o imóvel entrou ou de onde saiu, inclusive quando sai sozinho porque foi vendido ou ficou indisponível. Tirar o imóvel de um portal não gera evento.property:update: as contas que têm o imóvel publicado no próprio site.property:delete: as contas que tinham o imóvel em qualquer integração, site ou portal. Pode chegar para umpropertyIdque você nunca recebeu; nesse caso, ignore.lead:create: a conta responsável pela lead. Se ela for de corretor vinculado a uma imobiliária, o evento vai só para os webhooks da imobiliária, e ouserSlugdiz qual corretor recebeu.
Um endpoint para vários clientes. Se você integra mais de uma conta, pode
cadastrar a mesma URL e o mesmo token em todas. Quando mais de uma delas tem o
mesmo imóvel, o evento chega uma vez só. Use o userSlug dos eventos de
publicação e de lead para saber de qual conta ele é.
Como testar
- Imóveis: coloque um imóvel no site e tire em seguida. Chegam um
publication:createe umpublication:delete. - Leads: crie uma lead pela API de leads. Ela
gera um
lead:create, mas é uma lead de verdade: entra no CRM da conta e avisa o responsável por WhatsApp e e-mail.