Webhooks

Hay eventos que no los disparás vos: alguien deja una consulta en un aviso de tu cliente y el portal nos avisa a nosotros. Para que te lleguen, declarás una vez a qué URL tuya te los entregamos.

PUT     /property-v1/webhooks/{event}
GET     /property-v1/webhooks/{event}
DELETE  /property-v1/webhooks/{event}

Las tres piden el scope property-api-webhooks.

Mientras no declares la URL, las consultas de tus clientes no te llegan — y no quedan guardadas en ninguna parte. No hay una bandeja nuestra que las acumule hasta que registres el webhook: si el evento llega y no hay destino declarado, se descarta.

Y en los portales que sólo entregan por este camino, declararlo no es una preferencia: es la única forma de recibir las consultas. Cuáles son te lo dice Capacidades — el campo pushFromPortal de cada portal, y ahí mismo vas a ver si además podés ir a buscarlas vos (pull).

Los eventos

{event}Cuándo te llega
leadalguien dejó una consulta en un aviso de uno de tus clientes

Es un solo scope para todos los eventos, no uno por evento. Cuando sumemos uno nuevo vas a poder registrarlo con el token que ya tenés, sin pedirnos nada.

Declarar a dónde te entregamos

curl -X PUT "https://property-api.mapaprop.com/property-v1/webhooks/lead" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://tu-sistema.com/hooks/mapaprop-leads" }'
CampoObligatorioQué es
urlsítu endpoint. Tiene que ser https

La respuesta:

{
  "event": "lead",
  "url": "https://tu-sistema.com/hooks/mapaprop-leads",
  "scope": "app",
  "secret": "9f2c…",
  "createdAt": "2026-10-20T14:02:11.004Z",
  "updatedAt": "2026-10-20T14:02:11.004Z"
}

El secret se devuelve una sola vez. Guardalo cuando lo recibís: no hay ningún método que te lo vuelva a dar. Si lo perdés, registrás el webhook de nuevo y se emite uno nuevo — el anterior deja de servir.

Sirve para verificar que lo que te llega es nuestro: firmamos cada entrega con él.

Volver a registrar pisa la URL

Mandar el PUT otra vez reemplaza la URL. El createdAt no cambia, y el secreto sí: por eso registrar de nuevo es también la forma de rotarlo.

La URL tiene que ser https

Sin excepciones, tampoco para pruebas. Lo que entregamos son datos personales de un tercero —el nombre, el email y el teléfono de quien consultó— y por http viajarían en claro. Para probar en tu máquina, usá un túnel: ya te da https.

Un destino distinto para un cliente puntual

Por defecto, todo lo de todos tus clientes va a una URL. Si necesitás que las de un cliente vayan a otro lado, mandá su x-client-ref:

curl -X PUT "https://property-api.mapaprop.com/property-v1/webhooks/lead" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-client-ref: inmo-42" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://tu-sistema.com/hooks/inmo-42" }'

Eso crea una excepción sólo para ese cliente ("scope": "client"): el resto sigue yendo al default. Cuando llega un evento de inmo-42 buscamos primero su excepción y, si no existe, usamos el default.

El header es opcional acá, y es la única parte de la API donde lo es. Omitirlo no es un olvido: significa "éste es el destino de todos mis clientes".

Ver qué tenés declarado

curl "https://property-api.mapaprop.com/property-v1/webhooks/lead" \
  -H "Authorization: Bearer $TOKEN"
{
  "event": "lead",
  "url": "https://tu-sistema.com/hooks/mapaprop-leads",
  "scope": "app",
  "createdAt": "2026-10-20T14:02:11.004Z",
  "updatedAt": "2026-10-20T14:02:11.004Z"
}

El secreto no sale acá. Si no lo tenés, volvé a registrar el webhook.

Agregá el x-client-ref para consultar la excepción de un cliente en vez del default.

Lo que te entregamos

Un POST a tu URL, con el evento en un sobre:

{
  "event": "lead",
  "portal": "argenpropapi",
  "clientRef": "inmo-42",
  "lead": {
    "id": "77349380",
    "propertyCode": "6NN6_01M49G0SE61P525JYAKKQ0T700",
    "name": "Ana",
    "email": "ana@ejemplo.com",
    "phone": "1155556666",
    "message": "Hola, sigue disponible?",
    "date": "2026-10-20T11:30:00",
    "portalMessageId": null,
    "portalAdId": "20703571",
    "portalContactId": null,
    "portalActionId": null
  }
}
Campo del sobreQué es
eventqué pasó. Hoy siempre lead
portalde qué portal viene — el mismo nombre que usás para publicar y en tu scope
clientRefde qué cliente tuyo es

Los campos de lead son los mismos de Consultas, con el mismo significado: es el mismo objeto, así que no tenés que escribir dos lecturas del mismo hecho.

propertyCode es el código que escribió tu publicación, así que es con lo que cruzás la consulta contra la propiedad de tu lado. Lo arma cada portal a su manera —en Argenprop el código de publicación de la inmobiliaria va adelante del paId, en Zonaprop va un sufijo atrás—, por eso no lo parsees: compará contra el que te devolvió el alta.

portal y clientRef viajan en el sobre y no adentro de lead porque los necesitás para saber a quién pertenece lo que te llega: una URL de alcance app recibe las consultas de todos tus clientes y de todos los portales.

Verificar que te lo mandamos nosotros

Cada entrega lleva el header X-Mapaprop-Signature:

X-Mapaprop-Signature: t=1792500000,v1=5f8a…
  • t es el momento de la entrega, en segundos.
  • v1 es HMAC-SHA256 de <t>.<cuerpo crudo> con tu secret, en hexadecimal.
const esperado = crypto.createHmac('sha256', secret)
  .update(`${t}.${cuerpoCrudo}`)
  .digest('hex');

Firmá el cuerpo crudo, el que llegó. Si lo parseás y lo volvés a serializar, cualquier diferencia de espacios o de orden de claves da otra firma y no va a coincidir nunca.

Compará con crypto.timingSafeEqual y rechazá si t tiene más de 5 minutos: así una entrega vieja capturada no te sirve a nadie para reenviarla.

Qué nos tenés que contestar

ContestásQué hacemos
2xxlisto, no te la volvemos a mandar
cualquier otra cosa, o no contestásreintentamos

Cuántas veces reintentamos, exactamente

Tres intentos en total —el primero más dos reintentos—, con unos 2 minutos entre uno y otro. Después de los tres, dejamos de intentar y esa consulta no vuelve: no queda en una cola esperando a que arregles tu endpoint ni hay forma de pedir que la reenviemos.

Son unos 4 minutos de margen. Si tu endpoint se cae por más que eso, lo que entró en esa ventana se perdió.

Por eso conviene que tu endpoint haga lo mínimo antes de contestar: guardá el sobre crudo y contestá 2xx; procesalo después, con tus propios reintentos. Un endpoint que valida, enriquece y escribe en tres sistemas antes de contestar es un endpoint que se cae más seguido que el que sólo guarda.

Si guardás la consulta de forma asincrónica, contestá recién después de haberla guardado: un 2xx adelantado nos dice que ya la tenés, y si tu guardado falla después, esa consulta no vuelve.

Dedupeá por lead.id dentro de un mismo portal: un reintento nuestro trae el mismo valor.

Dejar de recibir

curl -X DELETE "https://property-api.mapaprop.com/property-v1/webhooks/lead" \
  -H "Authorization: Bearer $TOKEN"
{ "deleted": true, "event": "lead" }

Borrar algo que no estaba registrado responde {"deleted": false} con 200 — no es un error, así que podés llamarlo sin chequear antes.

Con x-client-ref borrás sólo la excepción de ese cliente, y sus eventos vuelven a ir al default de tu app.

Los errores

CódigoerrorQué pasó
400url is required · url must use https · url must be a valid absolute URL · url must be at most 512 charsrevisá el campo url
400Invalid JSON bodyel cuerpo del PUT no es JSON válido
400event must be one of: leadel {event} de la ruta no es uno de los de arriba
403Missing scope: property-api-webhookstu token no tiene el scope. Escribinos
404webhook_not_foundno tenés nada declarado para ese evento (sólo en el GET)

Los 400 traen además field con el nombre del campo, para que no tengas que leer el texto.

Lo que esto NO hace

  • No guarda nada. Te entregamos y seguimos: no hay una bandeja nuestra donde los eventos queden esperando a que los consultes, ni antes de que declares la URL ni después de los tres intentos.
  • No es un sustituto universal de ir a buscarlas. Los dos caminos existen, pero no todos los portales tienen los dos: hay portales que sólo empujan y portales que sólo se consultan. Antes de elegir, mirá Capacidades — es el mismo dato que usa nuestro código, así que no te queda hardcodeado nada.

Ver también

  • Recibir consultas — el flujo, y cuál de los dos caminos te conviene
  • Leads — pedirlas vos, en vez de que te lleguen