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 |
|---|---|
lead | alguien 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" }'
| Campo | Obligatorio | Qué es |
|---|---|---|
url | sí | 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 sobre | Qué es |
|---|---|
event | qué pasó. Hoy siempre lead |
portal | de qué portal viene — el mismo nombre que usás para publicar y en tu scope |
clientRef | de 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…
tes el momento de la entrega, en segundos.v1esHMAC-SHA256de<t>.<cuerpo crudo>con tusecret, 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ás | Qué hacemos |
|---|---|
2xx | listo, no te la volvemos a mandar |
| cualquier otra cosa, o no contestás | reintentamos |
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ódigo | error | Qué pasó |
|---|---|---|
400 | url is required · url must use https · url must be a valid absolute URL · url must be at most 512 chars | revisá el campo url |
400 | Invalid JSON body | el cuerpo del PUT no es JSON válido |
400 | event must be one of: lead | el {event} de la ruta no es uno de los de arriba |
403 | Missing scope: property-api-webhooks | tu token no tiene el scope. Escribinos |
404 | webhook_not_found | no 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