para imobiliárias
Ligue o CRM que a imobiliária já usa e a carteira entra no site sozinha: preço novo, foto nova, imóvel reservado, vendido. Ninguém redigita nada — e os contatos dos anúncios vão direto para você.
PUT /api/v1/listings/REF-1042
Authorization: Bearer mk_9f3c…
{
"title": "Apartamento 2 quartos na Velha",
"operation": "aluguel",
"type": "apartamento",
"city": "Blumenau",
"uf": "SC",
"price": 2100
}201 Created
{
"external_id": "REF-1042",
"url": "https://tudonuma.com/imovel/…-aa8fde4f9382",
"status": "active",
"created": true
}O CRM é a fonte da verdade. Mudou o preço, entrou foto, o imóvel foi reservado ou vendido: a Tudonuma recebe a mudança do próprio CRM, sem ninguém abrir o painel.
Sem plano pago, sem destaque pago, sem comissão. Hoje a Tudonuma não cobra nada para anunciar — pela API ou pelo painel.
Anúncio publicado pela imobiliária mostra os botões dela: mensagem no site, WhatsApp e telefone. As cópias reunidas de portais só levam ao anúncio de origem.
Uma página pública da imobiliária, com logo, capa, descrição, contatos e todos os anúncios ativos — atualizada junto com o CRM.
No painel, por anúncio e nos últimos 30 dias: quantas vezes apareceu, quantos abriram, salvaram nos favoritos e pediram contato.
Aprovou ou recusou, o seu sistema recebe um webhook assinado, com o motivo. Dá para abrir a tarefa para o corretor na hora.
Cadastro de imobiliária com CNPJ, grátis. No painel, Integrações → Chaves de API: a chave aparece uma única vez — guarde no cofre do CRM.
O fornecedor do CRM, um programador ou um agente de IA envia os imóveis com PUT, usando o código do CRM como external_id. A primeira chamada cria; as seguintes atualizam.
Cadastre uma URL https:// e receba listing.approved e listing.rejected, assinados com HMAC.
Não temos plugin pronto para nenhum CRM — é uma API REST comum, e qualquer sistema integra. Escolha quem faz:
Vista, Jetimob, Kenlo, Superlógica, Imoview, Tecimob… Mande ao suporte do seu CRM uma mensagem pronta, com a documentação e o que você quer ligar.
Claude Code, Cursor, Codex, ChatGPT: o prompt desta página já aponta para a documentação em Markdown e para o OpenAPI, e diz o que fazer, na ordem certa.
Com poucos imóveis, publique pelo painel: o assistente guia campo por campo. Quer ajuda para ligar o CRM? A equipe conversa com você.
Três escolhas e sai o texto para colar no Claude Code, no Cursor, no Codex ou no ChatGPT — ou a mensagem para o suporte do seu CRM. Os dois levam os links certos e o que costuma dar errado.
tudonuma.com/llms.txtÍndice para agentes (llms.txt)tudonuma.com/api/v1/docs.mdA documentação em Markdowntudonuma.com/api/v1/openapi.jsonA especificação OpenAPI 3.0Você vai integrar o Vista da nossa imobiliária à Tudonuma (https://tudonuma.com), um portal de imóveis, pela API de integração dela. É grátis e não tem plugin pronto: são chamadas REST comuns.
Leia a documentação antes de escrever código:
- https://tudonuma.com/api/v1/docs.md — a documentação completa, em Markdown
- https://tudonuma.com/api/v1/openapi.json — a especificação OpenAPI 3.0
- https://tudonuma.com/integracao — a mesma documentação para pessoas, com um validador de payload
O essencial:
- Base URL: https://tudonuma.com/api/v1 — JSON UTF-8, campos em snake_case, Content-Type: application/json.
- Autenticação: cabeçalho Authorization: Bearer <chave>. A chave (começa com mk_) vem da variável de ambiente TUDONUMA_API_KEY — nunca no código, no repositório nem em log.
- Cada imóvel é identificado pelo código dele no Vista: é o external_id (1 a 100 caracteres: letras sem acento, dígitos e - _ . :). PUT /listings/{external_id} cria na primeira vez e atualiza nas seguintes, então repetir a carga não duplica.
- Limite: 600 chamadas por minuto por chave.
- Use a linguagem e o estilo do projeto em que você estiver; se não houver projeto, proponha um antes de começar.
Faça, nesta ordem:
1. Um teste de conexão com GET /me, que responde com a imobiliária dona da chave e os escopos dela.
2. O mapeamento dos campos do Vista para os da Tudonuma. Obrigatórios na criação: title, operation ("aluguel" ou "venda"), type, address, city, uf e price (em reais) ou price_cents. Mande também lat/lng e ao menos uma foto (URLs públicas e estáveis): sem coordenadas o anúncio não aparece no mapa, e sem foto não entra na busca.
3. Publicar e atualizar: a cada alteração no Vista, um PUT /listings/{external_id} com o registro COMPLETO — alguns campos omitidos voltam ao padrão. Imóvel vendido ou alugado: DELETE /listings/{external_id}. Reservado por um tempo: POST /listings/{external_id}/status com {"status": "paused"} (e "active" para voltar).
4. Conciliação noturna: a carteira inteira em lotes de até 200 por POST /listings/batch (~50 por lote quando houver muitas fotos novas, que são baixadas durante a chamada); depois GET /listings?status=all (100 por página) e DELETE do que não existe mais no Vista.
5. Webhooks: um endpoint HTTPS que confere o cabeçalho X-Tudonuma-Signature (sha256=HMAC-SHA256 do corpo BRUTO, antes de qualquer parse de JSON, com o secret whsec_… do webhook; compare em tempo constante), responde 2xx em até 10 segundos (processe depois, numa fila), ignora X-Tudonuma-Delivery repetido e, em listing.rejected, cria uma tarefa para o corretor com o moderation_note. Registre a URL no painel ou por POST /webhooks.
6. Erros: teste o campo error das respostas 4xx (413, 415 e 422 vêm em texto simples); em 429 e 5xx, tente de novo com espera crescente. Registre em log os warnings que o PUT devolve (uma foto pulada, por exemplo).
Antes de pôr no ar, valide um corpo de exemplo em https://tudonuma.com/integracao#validador e rode tudo contra um ou dois imóveis de teste. Quando terminar, me diga o que falta configurar do meu lado (a chave, a URL do webhook, o agendamento da carga noturna).A chave não vai no texto: o prompt pede que ela fique na variável TUDONUMA_API_KEY. Nunca cole a chave num chat.
O que as imobiliárias perguntam primeiro. O resto está na documentação logo abaixo — ou com a equipe.
Nada. Anunciar na Tudonuma é grátis para imobiliárias, pela API ou pelo painel: não há plano pago, destaque pago nem comissão.
Ainda não temos plugin pronto com nenhum CRM específico. A API é REST e genérica: o fornecedor do CRM, um programador ou um agente de IA liga qualquer sistema. A mensagem pronta ajuda a pedir ao seu fornecedor.
Hoje não. A forma automática de levar a carteira é esta API, em JSON. Com poucos imóveis, o painel resolve; se precisa de outro formato, fale com a equipe.
Alguém precisa escrever as chamadas uma vez — o fornecedor do CRM, um programador ou um agente de IA com o prompt desta página. Depois disso a sincronização roda sozinha.
A Tudonuma reúne anúncios públicos de portais. Essas cópias não se juntam sozinhas aos anúncios da sua conta: depois de confirmar a imobiliária, a equipe cuida delas. O que a imobiliária publica direto sai com os contatos dela.
Pode, mas escolha um caminho por imóvel: um anúncio do painel enviado de novo pela API vira um segundo anúncio, porque o do painel não tem external_id. Exclua o do painel, ou deixe esse imóvel fora da carga.
O status vira rejected, o motivo vem em moderation_note e o seu sistema recebe o webhook listing.rejected. Corrija e envie o PUT de novo: o anúncio volta para análise.
Guardamos só o hash SHA-256 dela; a chave completa aparece uma vez, ao criar. Se vazar, revogue no painel: ela para de funcionar na hora. Só o responsável pela conta da imobiliária gerencia as chaves.
Base https://tudonuma.com/api/v1 · JSON UTF-8 · campos em snake_case · exemplos em cURL, Node.js, PHP e Python.
Toda chamada leva a chave no cabeçalho Authorization. X-Api-Key: mk_… também é aceito, para CRMs que não deixam mexer no Authorization.
Guardamos apenas o hash SHA-256 da chave: se ela se perder, revogue no painel e crie outra. Uma chave revogada — ou de uma conta desativada — para de funcionar na hora, com 401.
| Escopo | Libera |
|---|---|
listings:read | GET /listings e GET /listings/{external_id} |
listings:write | PUT, /batch, /status e DELETE |
webhooks:manage | /webhooks por API |
Uma chave nova recebe os três escopos. GET /me funciona com qualquer um — é o jeito mais simples de testar se a chave está certa.
curl -X GET 'https://tudonuma.com/api/v1/me' \
-H "Authorization: Bearer mk_SUA_CHAVE"Os erros de negócio vêm sempre com o mesmo corpo: um error estável, que o seu código pode testar, e um message para quem estiver lendo o log.
Limite: 600 chamadas por minuto por chave. Para enviar a carteira inteira, use /listings/batch — são 200 anúncios por chamada.
Além do limite por chave existe um limite por IP na borda (cerca de 60 req/s, com rajada de 30), e esse 429 vem sem corpo JSON. Nos dois casos, espere alguns segundos e tente de novo com espera crescente.
{
"error": "invalid_price",
"message": "price (reais) or price_cents must be positive"
}| Status | Quando acontece | O que fazer |
|---|---|---|
| 400 | Dados inválidos. O error diz qual campo: invalid_price, invalid_type… | Corrija o registro no CRM; tentar de novo não resolve. |
| 401 | Chave ausente, desconhecida, revogada, ou conta desativada. | Confira a chave no painel. |
| 403 | scope_missing: a chave não tem o escopo da rota. | Crie uma chave com o escopo. |
| 404 | not_found: nenhum anúncio com esse external_id nesta conta. | Crie com PUT. |
| 409 | not_applicable: por exemplo, reativar um anúncio retido pela moderação. | Leia moderation_note, corrija e envie o PUT. |
| 413 | Corpo acima de 2 MB. | Divida o lote. |
| 415 | Faltou Content-Type: application/json. | Envie o cabeçalho. |
| 422 | Tipo errado num campo: "bedrooms": "2" em texto, area_m2 com decimal. | Envie números como números. |
| 429 | rate_limited: mais de 600 chamadas no último minuto. | Espere e use o lote. |
| 500 | internal_error: falha do nosso lado. | Tente de novo com espera. |
413, 415 e 422 vêm em texto simples, sem o corpo {error, message}.
Todo anúncio é identificado pelo código que o CRM já usa — REF-1042, AP0231, o que for. PUT /listings/{external_id} cria na primeira chamada e atualiza nas seguintes. Rodar a sincronização inteira toda noite não gera duplicatas.
O código vale por conta: o mesmo external_id em duas contas são dois anúncios.
Formato: de 1 a 100 caracteres — letras sem acento, dígitos e - _ . :. Espaços e barras não entram; troque por - antes de enviar.
PUT /listings/REF-1042Cada anúncio tem dois campos que andam juntos e dizem coisas diferentes: status é o ciclo de vida, que você controla; moderation é o parecer da equipe, que você só lê.
Se o anúncio aparece no site.
available: true.pre).moderation_note.O parecer da equipe sobre o conteúdo.
listing.approved.listing.rejected.O modo da plataforma está em GET /me: post publica na hora e revisa depois; pre segura em pending_review até aprovar.
PUT de um código novo→active+pendingmodo postavailable: false ou /status paused→pausedlisting.rejectedPUT de novo→volta para análise: active+pendingDELETE→deleted+ webhook listing.deleted/meA imobiliária dona da chave, os escopos dela e o modo de moderação da plataforma. Use no botão “testar conexão” do seu CRM.
curl -X GET 'https://tudonuma.com/api/v1/me' \
-H "Authorization: Bearer mk_SUA_CHAVE"{
"agency": {
"id": 42,
"name": "Imobiliária Exemplo",
"slug": "imobiliaria-exemplo"
},
"key": {
"id": 7,
"scopes": [
"listings:read",
"listings:write",
"webhooks:manage"
]
},
"user": {
"id": 1180,
"email": "integracao@exemplo.com.br"
},
"moderation": "post"
}/listingslistings:read100 por página, do mais novo para o mais antigo. Serve para conciliar: compare com a carteira do CRM e remova o que não existe mais.
| Parâmetro | Descrição |
|---|---|
pageinteger · padrão 1 | Página, a partir de 1. |
statusstring · padrão all | active, paused, rejected, pending_review ou all (tudo menos os excluídos). |
curl -X GET 'https://tudonuma.com/api/v1/listings?page=1&status=all' \
-H "Authorization: Bearer mk_SUA_CHAVE"{
"data": [
{
"id": 18231,
"external_id": "REF-1042",
"url": "https://tudonuma.com/imovel/apartamento-aluguel-velha-blumenau-aa8fde4f9382",
"status": "active",
"moderation": "approved",
"title": "Apartamento 2 quartos com sacada na Velha",
"price_cents": 210000,
"updated_at": "2026-09-21T03:10:05Z"
}
],
"total": 312,
"page": 1,
"page_size": 100
}/listings/{external_id}listings:readO anúncio como está agora: status, moderation, a URL pública e as fotos já servidas por nós. 404 se não existir nesta conta.
curl -X GET 'https://tudonuma.com/api/v1/listings/REF-1042' \
-H "Authorization: Bearer mk_SUA_CHAVE"{
"id": 18231,
"public_id": "aa8fde4f9382",
"external_id": "REF-1042",
"url": "https://tudonuma.com/imovel/apartamento-aluguel-velha-blumenau-aa8fde4f9382",
"status": "active",
"moderation": "approved",
"moderation_note": "",
"title": "Apartamento 2 quartos com sacada na Velha",
"operation": "aluguel",
"type": "apartamento",
"address": "Rua João Pessoa, 1250, ap. 302",
"neighborhood": "Velha",
"city": "Blumenau",
"uf": "SC",
"lat": -26.9245,
"lng": -49.0912,
"area_m2": 72,
"bedrooms": 2,
"bathrooms": 1,
"parking": 1,
"price_cents": 210000,
"condo_cents": 38000,
"iptu_cents": 6000,
"description": "Apartamento arejado, sol da manhã, a duas quadras da Rua São Paulo.",
"photos": [
"https://tudonuma.com/uploads/2026/09/7c1e…a4.webp",
"https://tudonuma.com/uploads/2026/09/03b9…1f.webp"
],
"matricula": "45.123",
"inscricao": "01.02.003.0456.001",
"created_at": "2026-09-07T14:02:48Z",
"updated_at": "2026-09-21T03:10:05Z"
}/listings/{external_id}listings:writeO coração da integração. Responde 201 Created quando cria e 200 OK quando atualiza, sempre com o anúncio como ficou, mais created e warnings.
Obrigatórios na criação: title, operation, type, address, city, uf e price (ou price_cents). Todos os campos estão na referência.
Parte dos campos mantém o valor anterior quando omitida; outra parte volta ao padrão — a referência marca cada um. photos e tags, quando enviados, substituem a lista inteira.
URLs públicas http(s)://, JPG, PNG ou WebP, até 10 MB e 12000×12000 px. Baixamos e servimos as fotos nós mesmos. Cada URL é baixada uma vez e lembrada — se a imagem mudar, publique numa URL nova. Redirecionamentos não são seguidos. Uma foto que falhar é pulada e aparece em warnings; o anúncio é salvo mesmo assim.
Um anúncio sem nenhuma foto é salvo, mas não entra no catálogo público.
Monte o corpo no validador antes de programar: ele aponta o mesmo erro que a API devolveria.
curl -X PUT 'https://tudonuma.com/api/v1/listings/REF-1042' \
-H "Authorization: Bearer mk_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"title": "Apartamento 2 quartos com sacada na Velha",
"operation": "aluguel",
"type": "apartamento",
"address": "Rua João Pessoa, 1250, ap. 302",
"neighborhood": "Velha",
"city": "Blumenau",
"uf": "SC",
"lat": -26.9245,
"lng": -49.0912,
"area_m2": 72,
"bedrooms": 2,
"bathrooms": 1,
"parking": 1,
"floor": 3,
"building_floors": 8,
"price": 2100,
"condo": 380,
"iptu": 60,
"description": "Apartamento arejado, sol da manhã, a duas quadras da Rua São Paulo.",
"photos": [
"https://cdn.suaimobiliaria.com.br/imoveis/1042/01.jpg",
"https://cdn.suaimobiliaria.com.br/imoveis/1042/02.jpg"
],
"furnished": "partial",
"pet_friendly": true,
"amenities": [
"piscina",
"academia",
"portaria-24h"
],
"tags": [
"sacada",
"churrasqueira"
],
"contact_phone": "47999990000",
"matricula": "45.123",
"inscricao": "01.02.003.0456.001",
"available": true
}'{
"id": 18231,
"public_id": "aa8fde4f9382",
"external_id": "REF-1042",
"url": "https://tudonuma.com/imovel/apartamento-aluguel-velha-blumenau-aa8fde4f9382",
"status": "active",
"moderation": "pending",
"moderation_note": "",
"…": "o anúncio completo, como em GET",
"created": true,
"warnings": []
}/listings/batchlistings:writeCada item leva o seu external_id junto dos campos e é julgado sozinho: um registro ruim não derruba o lote. A resposta lista cada item com ok, e o erro dos que falharam.
Um JSON com tipo errado em qualquer item recusa a chamada inteira (422). O corpo vai até 2 MB.
As fotos novas são baixadas durante a chamada. Com muitas fotos novas, prefira lotes de ~50 para não passar de 60 s. Um lote de atualizações sem fotos novas pode ir cheio.
curl -X POST 'https://tudonuma.com/api/v1/listings/batch' \
-H "Authorization: Bearer mk_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"listings": [
{
"external_id": "REF-1042",
"title": "Apartamento 2 quartos na Velha",
"operation": "aluguel",
"type": "apartamento",
"address": "Rua João Pessoa, 1250",
"city": "Blumenau",
"uf": "SC",
"price": 2100
},
{
"external_id": "REF-1043",
"title": "Casa 3 quartos no Garcia",
"operation": "venda",
"type": "casa",
"address": "Rua Amazonas, 310",
"city": "Blumenau",
"uf": "SC",
"price": 650000
}
]
}'{
"ok": 1,
"failed": 1,
"results": [
{
"external_id": "REF-1042",
"ok": true,
"id": 18231,
"public_id": "aa8fde4f9382",
"created": false,
"warnings": []
},
{
"external_id": "REF-1043",
"ok": false,
"error": "invalid_price",
"message": "price (reais) or price_cents must be positive"
}
]
}/listings/{external_id}/statuslistings:write{"status": "paused"} ou {"status": "active"} — mais leve que um PUT quando o imóvel só saiu do ar por um tempo: reservado, em negociação.
Não reativa um anúncio retido pela moderação (409) e não dispara webhook.
curl -X POST 'https://tudonuma.com/api/v1/listings/REF-1042/status' \
-H "Authorization: Bearer mk_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"status": "paused"
}'{
"id": 18231,
"external_id": "REF-1042",
"status": "paused"
}/listings/{external_id}listings:writePara imóvel vendido ou alugado. É uma exclusão lógica: o anúncio sai do site e das listagens, o histórico fica. Dispara listing.deleted.
curl -X DELETE 'https://tudonuma.com/api/v1/listings/REF-1042' \
-H "Authorization: Bearer mk_SUA_CHAVE"{
"id": 18231,
"external_id": "REF-1042",
"status": "deleted"
}Tudo o que o PUT e o lote aceitam. Campos desconhecidos são ignorados — o CRM pode mandar o registro inteiro como está. A coluna Na atualização diz o que acontece com um campo que não vem num PUT de um anúncio que já existe.
| Campo | Tipo | Descrição | Na atualização |
|---|---|---|---|
titleobrig. | string | Título do anúncio, de 3 a 200 caracteres. | mantém |
operationobrig. | aluguel | venda | A operação. Maiúsculas são aceitas. | mantém |
typeobrig. | string | O tipo do imóvel; os valores estão logo abaixo da tabela. | mantém |
addressobrig. | string | Rua, número e complemento. | mantém |
cityobrig. | string | Cidade. Uma cidade que ainda não está no cadastro é criada. | mantém |
ufobrig. | string | Estado, duas letras. | mantém |
priceobrig. | number | Preço em reais — aluguel mensal ou valor de venda. Ou envie price_cents. | mantém |
price_cents | integer | Preço em centavos. Vale este se vierem os dois. | mantém |
neighborhood | string | Bairro pelo nome; é casado com o cadastro da cidade, sem ligar para acentos. | mantém |
lat | number | Latitude. Sem coordenadas o anúncio não aparece no mapa. | mantém |
lng | number | Longitude. | mantém |
area_m2 | integer | Área útil em m², número inteiro. | mantém |
bedrooms | integer | Quartos, até 99. | mantém |
bathrooms | integer | Banheiros, até 99. | mantém |
parking | integer | Vagas de garagem, até 99. | mantém |
condo | number | Condomínio mensal em reais. Ou condo_cents. | mantém |
iptu | number | IPTU mensal em reais. Ou iptu_cents. | mantém |
description | string | Texto livre, até 8.000 caracteres. | mantém |
photos | string[] | URLs públicas http(s), até 16; JPG, PNG ou WebP de até 10 MB. Enviada, substitui a lista. Sem nenhuma foto, o anúncio não entra no catálogo. | mantém |
tags | string[] | Características livres (sacada, vista-rio…). Até 40, de até 40 caracteres. Enviada, substitui a lista. | mantém |
floor | integer | Andar. 0 = térreo. | volta ao padrão |
building_floors | integer | Andares do prédio. | volta ao padrão |
furnished | bool | no | partial | full | Mobília. true equivale a full. | volta ao padrão |
pet_friendly | bool | Aceita animais. | volta ao padrão |
internet | bool | Internet incluída. | volta ao padrão |
amenities | string[] | Áreas comuns do condomínio. As da lista abaixo viram filtro no site; as outras são guardadas sem filtro. | volta ao padrão |
contact_phone | string | Telefone com DDD. Guardamos só os dígitos. | volta ao padrão |
show_whatsapp | bool | Mostra o botão de WhatsApp. Padrão true. | volta ao padrão |
show_phone | bool | Mostra o telefone. Padrão true. | volta ao padrão |
show_chat | bool | Aceita mensagens pelo chat do site. Padrão true. | volta ao padrão |
matricula | string | Matrícula do cartório, até 64 caracteres. Com ela o anúncio entra no filtro “com matrícula”. | volta ao padrão |
inscricao | string | Inscrição imobiliária (IPTU), até 64 caracteres. | volta ao padrão |
owner_type | owner | agency | Quem detém o imóvel. Padrão agency. | volta ao padrão |
available | bool | false pausa (oculto, não excluído); true publica. Omitido, o status não muda. | — |
apartamentostudiocasaloftcolivingcoberturaconsultoriogalpaocomercialterrenoterreno_comerciallojasalapredioruralpiscinaacademiachurrasqueirasalao-festasespaco-kidspet-areacoworkingportaria-24hsaunamercadinhoOutros valores de amenities são guardados, mas não viram filtro.
Cole o JSON que o seu CRM gera. O validador aplica as mesmas regras da API, na mesma ordem, e mostra a resposta que ela daria — sem enviar nada. Quando passa, sai o comando pronto para enviar de verdade.
Seria criado, sem avisos.
curl -X PUT 'https://tudonuma.com/api/v1/listings/REF-1042' \
-H "Authorization: Bearer mk_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"title": "Apartamento 2 quartos com sacada na Velha",
"operation": "aluguel",
"type": "Apartamento",
"address": "Rua João Pessoa, 1250, ap. 302",
"neighborhood": "Velha",
"city": "Blumenau",
"uf": "SC",
"lat": -26.9245,
"lng": -49.0912,
"area_m2": 72,
"bedrooms": 2,
"bathrooms": 1,
"parking": 1,
"floor": 3,
"building_floors": 8,
"price": 2100,
"condo": 380,
"iptu": 60,
"description": "Apartamento arejado, sol da manhã, a duas quadras da Rua São Paulo.",
"photos": [
"https://cdn.suaimobiliaria.com.br/imoveis/1042/01.jpg",
"https://cdn.suaimobiliaria.com.br/imoveis/1042/02.jpg"
],
"furnished": "partial",
"pet_friendly": true,
"amenities": [
"piscina",
"academia",
"elevador"
],
"tags": [
"sacada",
"churrasqueira"
],
"contact_phone": "47999990000",
"matricula": "45.123",
"inscricao": "01.02.003.0456.001",
"available": true
}'Cadastre uma URL https:// em Painel → Integrações → Webhooks, ou por API. A cada evento enviamos um POST JSON com o anúncio como ficou.
| Evento | Quando |
|---|---|
listing.created | Um PUT criou o anúncio (só via API). |
listing.updated | Um PUT atualizou o anúncio (só via API). |
listing.deleted | Um DELETE removeu o anúncio (só via API). |
listing.approved | A moderação aprovou um anúncio da imobiliária — venha ele da API ou do painel. |
listing.rejected | A moderação recusou. O motivo vem em moderation_note. |
ping | O teste disparado pelo botão do painel. |
X-Tudonuma-Event | O nome do evento. |
X-Tudonuma-Delivery | Id único da entrega. Guarde e ignore repetições. |
X-Tudonuma-Signature | sha256=<hex>, HMAC-SHA256 do corpo bruto com o secret do webhook. |
User-Agent | Tudonuma-Webhooks/1.0 |
{
"event": "listing.rejected",
"created_at": "2026-09-07T14:03:11Z",
"data": {
"listing": {
"id": 18231,
"public_id": "aa8fde4f9382",
"external_id": "REF-1042",
"url": "https://tudonuma.com/imovel/apartamento-aluguel-velha-blumenau-aa8fde4f9382",
"status": "rejected",
"moderation": "rejected",
"moderation_note": "Fotos pertencem a outro imóvel.",
"…": "o anúncio completo"
}
}
}Antes de confiar no payload, recalcule o HMAC do corpo exatamente como chegou — antes de qualquer JSON.parse — e compare em tempo constante.
O secret (whsec_…) aparece uma única vez, ao criar o webhook.
import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";
const SECRET = process.env.TUDONUMA_WEBHOOK_SECRET;
const app = express();
// O corpo BRUTO: express.raw, e não express.json.
app.post("/webhooks/tudonuma", express.raw({ type: "application/json" }), (req, res) => {
const expected = "sha256=" + createHmac("sha256", SECRET).update(req.body).digest("hex");
const got = req.get("X-Tudonuma-Signature") ?? "";
if (got.length !== expected.length || !timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
return res.sendStatus(401);
}
res.sendStatus(204); // responda já; processe depois
const { event, data } = JSON.parse(req.body);
fila.push({ entrega: req.get("X-Tudonuma-Delivery"), event, anuncio: data.listing });
});Mude uma vírgula no corpo para ver a assinatura deixar de conferir. Nada sai desta página: o cálculo é feito no navegador.
Responda 2xx em até 10 segundos — de preferência antes de processar, pondo o evento numa fila. Qualquer outra resposta, um timeout ou um redirecionamento (3xx não é seguido) conta como falha, e a entrega é tentada de novo com espera crescente.
O painel mostra as entregas e o último status de cada webhook. Até 10 webhooks ativos por imobiliária; URL https:// pública de até 500 caracteres. As entregas saem uma por vez, a cada 15 s, e a ordem de chegada não é garantida — use o created_at do evento.
Para CRMs que se configuram sozinhos na instalação. Exige o escopo webhooks:manage.
| Rota | Faz |
|---|---|
GET /webhooks | Lista os webhooks e os eventos disponíveis. |
POST /webhooks | {url, events}; events vazio = todos. Responde 201 com o secret, uma única vez. |
DELETE /webhooks/{id} | Remove. |
curl -X POST 'https://tudonuma.com/api/v1/webhooks' \
-H "Authorization: Bearer mk_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"url": "https://crm.suaimobiliaria.com.br/webhooks/tudonuma",
"events": [
"listing.approved",
"listing.rejected"
]
}'{
"id": 3,
"url": "https://crm.suaimobiliaria.com.br/webhooks/tudonuma",
"events": [
"listing.approved",
"listing.rejected"
],
"active": true,
"created_at": "2026-09-07T13:58:20Z",
"last_status": null,
"last_delivered_at": null,
"failures": 0,
"secret": "whsec_4Tn2…"
}O desenho que funciona para a maioria dos CRMs: um fluxo contínuo para o que muda no dia, uma conciliação noturna para o que escapou, e os webhooks fechando o ciclo.
Ao salvar um imóvel no CRM, envie um PUT só dele. Vendido ou alugado: DELETE. Reservado por uns dias: /status paused.
Envie a carteira inteira em lotes de 200 pelo /batch. Depois leia GET /listings?status=all e apague o que não existe mais no CRM.
Em listing.rejected, crie uma tarefa para o corretor com o moderation_note. Recusa pede ação humana; reenviar igual não adianta.
lat/lng sempre que tiver — sem coordenadas o anúncio não aparece no mapa.price_cents) se o CRM já guarda assim; evita arredondamento.matricula e inscricao quando existirem: o anúncio entra no filtro “com matrícula”.X-Tudonuma-Delivery dos webhooks processados e ignore repetições./api/v1/openapi.json | Especificação OpenAPI 3.0 — importe no Postman, no Insomnia ou no gerador de cliente. | Abrir |
/api/v1/docs.md | Esta documentação em Markdown, para versionar junto do código da integração — ou para dar a um agente de IA. | Abrir |
/llms.txt | O índice para agentes de IA (padrão llms.txt): o que ler e onde. /llms-full.txt traz a documentação inteira num arquivo só. | Abrir |
| Painel → Integrações | Chaves, webhooks, histórico de entregas e o botão de teste (ping). |
Vai programar com um agente de IA? O prompt pronto já leva estes links. Dúvidas de integração: Falar com o suporte, no rodapé.
é grátis
Crie a conta da imobiliária, gere a chave e mande o pedido pronto para quem vai ligar.