Navegar na documentação

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"
  }
}
Toque ou passe o mouse num campo para ver o que ele significa.
  • Use o id do 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 changedFields e pelo updatedAt.
  • 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, ARCHIVED ou DELETED (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).
kind
CLIENT_FOR_PROPERTY ou PROPERTY_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) ou API.

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

  1. Na primeira leitura, mande since com a data de onde quer começar (até 30 dias atrás).
  2. Guarde o nextCursor e mande de volta em cursor na próxima leitura. Ele SEMPRE vem, também quando não há nada novo.
  3. Com hasMore: true, leia de novo na hora. Com false, espere alguns minutos.
  4. Para só alguns tipos, use types (separados por vírgula).
Siga o cursor, nunca o relógio. Um evento leva de segundos a mais de um minuto para aparecer. Quem pergunta “o que mudou desde 10:00?” perde o evento que chegou atrasado; o cursor não perde.

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.maxWebhooks em GET /v1/me.
  • Cada entrega é um POST com o corpo {type, timestamp, data} e três cabeçalhos: webhook-id (o id do evento), webhook-timestamp (segundos desde 1970) e webhook-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 updatedAt ou 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 410 desliga 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.