Eventos e webhooks
Saber o que mudou na conta sem reler a carteira: leia os eventos quando quiser, ou receba cada um no seu endereço, na hora.
Duas formas de saber o que mudou
- Ler os eventos (
GET /v1/events): o seu sistema pergunta quando quiser, em ordem, com 30 dias de memória. Não precisa de endereço público. - Receber por webhook: a ImobyFlow manda cada evento para um endereço seu, assinado, logo depois que ele acontece.
- Os dois levam o MESMO evento. O mais seguro é usar os dois: o webhook para reagir na hora e a leitura dos eventos para conferir o que pode ter se perdido.
O evento
O evento é enxuto: ids, o estado e os NOMES dos campos que mudaram (changedFields). Nunca nome, telefone, e-mail ou endereço. Para ver o valor novo, leia o recurso — assim o evento nunca chega desatualizado e não guarda dado pessoal.
{
"id": "evt_4f1c9a7e2b3d8c6a5e4f1b2c",
"type": "property.updated",
"createdAt": "2026-10-02T16:45:12.000Z",
"data": {
"id": "prop-3f9a1c2b7d4e",
"externalRef": "AP1234",
"status": "ACTIVE",
"changedFields": [
"salePrice"
],
"updatedAt": "2026-10-02T16:45:12.000Z"
}
}- Use o
iddo evento para não processar o mesmo evento duas vezes. - A escrita feita pela própria API também gera evento. O seu sistema reconhece a mudança que ele mesmo fez pelo
changedFieldse peloupdatedAt. - Cada tipo exige também a permissão de leitura do recurso: uma chave que só lê imóveis não vê os eventos de clientes.
Os tipos
A mesma lista sai em GET /v1/reference/event-types. Tipo novo pode chegar a qualquer momento: ignore o que o seu sistema não conhece.
property.createdproperties:read- Imóvel cadastrado.
idexternalRefstatusupdatedAt property.updatedproperties:read- Imóvel alterado (campos em changedFields).
idexternalRefstatuschangedFieldsupdatedAt property.deletedproperties:read- Imóvel na lixeira.
idexternalRefstatusupdatedAt property.restoredproperties:read- Imóvel tirado da lixeira.
idexternalRefstatusupdatedAt property.purgedproperties:read- Imóvel excluído de vez.
idexternalRefstatusupdatedAt lead.createdleads:read- Cliente cadastrado.
idexternalRefstatestatusownerAccountIdupdatedAt lead.updatedleads:read- Cliente alterado (campos em changedFields).
idexternalRefstatestatusownerAccountIdchangedFieldsupdatedAt lead.deletedleads:read- Cliente na lixeira.
idexternalRefstatestatusownerAccountIdupdatedAt lead.restoredleads:read- Cliente tirado da lixeira.
idexternalRefstatestatusownerAccountIdupdatedAt lead.purgedleads:read- Cliente excluído de vez.
idexternalRefstatestatusownerAccountIdupdatedAt radar.client_summaryradar:read- Oportunidades novas de um cliente, reunidas em alguns minutos.
leadIdnewMatchestopScoresinceuntil partnership.createdpartnerships:read- Parceria criada.
idstatuskind partnership.updatedpartnerships:read- Parceria mudou de etapa ou de prazo.
idstatuspreviousStatuschangedFields captacao.offer_receivedcaptacao:read- Proprietário chegou à vez da conta, com o prazo.
iddeadline captacao.offer_acceptedcaptacao:read- Proprietário aceito pela conta.
idacceptedAtvia captacao.offer_withdrawncaptacao:read- Proprietário saiu da vez da conta (prazo vencido ou aceito por outra).
id
O que vem em data
id- O id do objeto — imóvel, cliente, parceria ou proprietário da Captação.
externalRef- O código do seu sistema, quando o objeto tem um.
status- O status atual.
state- Onde o cliente está:
ACTIVE,ARCHIVEDouDELETED(lixeira). ownerAccountId- O corretor responsável pelo cliente.
changedFields- Os NOMES dos campos que mudaram — o valor novo, você lê no recurso.
updatedAt- A última alteração (UTC).
kindCLIENT_FOR_PROPERTYouPROPERTY_FOR_CLIENT.previousStatus- A etapa anterior, quando foi a etapa que mudou.
leadId- O cliente.
newMatches- Quantas oportunidades novas nasceram na janela.
topScore- A maior compatibilidade entre elas (0 a 100).
since- O começo da janela (UTC).
until- O fim da janela (UTC).
deadline- Até quando a conta pode aceitar (UTC).
acceptedAt- Quando a conta aceitou (UTC).
via- Por onde:
PANEL(a tela) ouAPI.
O radar.client_summary junta as oportunidades novas de um cliente que nasceram em alguns minutos: uma recomendação nova pode gerar dezenas delas, e um evento por oportunidade derrubaria o seu sistema. Só vale para cliente da própria carteira.
Ler os eventos
- Na primeira leitura, mande
sincecom a data de onde quer começar (até 30 dias atrás). - Guarde o
nextCursore mande de volta emcursorna próxima leitura. Ele SEMPRE vem, também quando não há nada novo. - Com
hasMore: true, leia de novo na hora. Comfalse, espere alguns minutos. - Para só alguns tipos, use
types(separados por vírgula).
Receber por webhook
- Cadastre o endereço pela API ou pelo painel (Minha conta › Integração por API). Só HTTPS, num endereço público. A resposta traz o segredo (
whsec_…) uma única vez: guarde-o. - Sem
types, o endereço recebe todos os tipos que a conta lê. Quantos endereços a conta pode ter:limits.maxWebhooksemGET /v1/me. - Cada entrega é um
POSTcom o corpo{type, timestamp, data}e três cabeçalhos:webhook-id(o id do evento),webhook-timestamp(segundos desde 1970) ewebhook-signature. - Responda com um status 2xx em até 10 segundos. Guarde o evento numa fila e processe depois: quem processa antes de responder estoura o prazo e recebe a entrega de novo.
- Sem 2xx, uma nova tentativa vem depois de 1 min, 5 min, 30 min, 2 h, 6 h, 12 h — cerca de 21 horas no total. A ordem de chegada não é garantida: decida pelo
updatedAtou leia o recurso. - O
webhook-idé o mesmo em todas as tentativas e no reenvio manual: é por ele que o seu sistema reconhece a repetição. - Redirecionamento não é seguido, e endereço com usuário e senha na URL é recusado (webhook_url_refused).
Uma entrega, como o seu servidor recebe
POST /imobyflow/eventos HTTP/1.1
Content-Type: application/json
User-Agent: ImobyFlow-Webhooks/1
webhook-id: evt_4f1c9a7e2b3d8c6a5e4f1b2c
webhook-timestamp: 1790959512
webhook-signature: v1,SvYNo/7lL4THMHE2HojUdVZTwMuEiUPa3y0HSpjtvtc=
{"type":"property.updated","timestamp":"2026-10-02T16:45:12.000Z","data":{"id":"prop-3f9a1c2b7d4e","externalRef":"AP1234","status":"ACTIVE","changedFields":["salePrice"],"updatedAt":"2026-10-02T16:45:12.000Z"}}Com o segredo whsec_pcwwPI/A9TWaDe9mPSrOkWlrkADDKDKjCKivhG986jw=, esta assinatura é válida — use-a para testar o seu código (desligue a janela de tempo só nesse teste).
Conferir a assinatura
A assinatura segue o padrão aberto Standard Webhooks — as bibliotecas dele servem. Para conferir à mão: HMAC-SHA256 de {webhook-id}.{webhook-timestamp}.{corpo}, com a chave que é o segredo depois de whsec_, decodificado do base64.
- Assine o corpo exatamente como chegou, antes de qualquer conversão de JSON. O JSON reformatado muda a acentuação e os espaços, e a assinatura deixa de bater.
- O cabeçalho pode trazer mais de uma assinatura, separadas por espaço: nas 24 horas depois de uma troca de segredo, vêm a do segredo novo e a do anterior. Aceite se qualquer uma bater.
- Compare com uma função de tempo constante — nunca com o igual comum.
- Recuse mensagem com mais de 5 minutos de diferença do seu relógio: sem isso, quem capturar uma entrega pode reenviá-la depois.
const crypto = require('node:crypto');
// rawBody: o corpo EXATAMENTE como chegou (string), antes de qualquer JSON.parse.
// No Express: app.post('/webhook', express.raw({ type: 'application/json' }), …) e req.body.toString('utf8').
function verifyWebhook(secret, msgId, timestamp, signatureHeader, rawBody) {
const now = Math.floor(Date.now() / 1000);
if (!/^\d+$/.test(timestamp) || Math.abs(now - Number(timestamp)) > 5 * 60) return false;
const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
const expected = crypto
.createHmac('sha256', key)
.update(`${msgId}.${timestamp}.${rawBody}`, 'utf8')
.digest();
return signatureHeader.split(' ').some((part) => {
const [version, sig] = part.split(',');
if (version !== 'v1' || !sig) return false;
const given = Buffer.from(sig, 'base64');
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
});
}
Cada um destes trechos é executado nos nossos testes contra a assinatura de verdade: válida, com duas assinaturas, corpo alterado, segredo errado, horário vencido e versão desconhecida.
Trocar o segredo
Gere um segredo novo. Durante 24 horas, as entregas vêm com as duas assinaturas — dá tempo de trocar no seu sistema sem perder evento.
Quando o endereço é desligado
- Responder
410desliga o endereço na hora. - Com pelo menos 5 entregas encerradas em 48 horas e 50% ou mais delas falhando depois de todas as tentativas, o endereço é desligado. Uma queda curta do seu lado não desliga: o que conta é a entrega que esgotou as tentativas.
- O dono da conta recebe um e-mail na hora. Desligado, as entregas seguintes não são feitas.
- Para voltar: religue o endereço (
status: ACTIVE), reenvie as entregas que importam e leia os eventos do período — eles ficam 30 dias.
Testar
O evento de teste entrega um webhook.test agora, assinado, e devolve o que o seu sistema respondeu. Ele não conta para o desligamento. As entregas mostram o status, as tentativas e o último código de cada uma.