Configurar
- Entra a Portal → Más → Webhooks.
- Escribe la URL de tu receptor: debe ser
https://y de un servidor público (no se aceptan IPs privadas nilocalhost). - Marca los Eventos que quieres recibir y pulsa Crear.
- Copia el secreto que aparece («Webhook creado. Secreto (guárdalo): …»): se muestra una sola vez y sirve para verificar la firma.
- Pulsa Probar para enviar un
lead.createdde prueba. La columna Último estado muestraok,http 4xx/http 5xxoerror: delivery failedcon la hora.
Los webhooks son por agente: recibes los eventos de tus propiedades y de los leads asignados a ti.
Gratis para agentes e inmobiliarias con licencia. Crea tu cuenta, verifica tu licencia y genera tu API key o tu widget en minutos.
Eventos
| Evento | Cuándo se envía | data |
|---|---|---|
lead.created | Al notificarte un lead nuevo: formularios de multilistado.mx, de tu sitio de Multilistado, del widget y colecciones. Las solicitudes de visita se envían cuando el comprador termina de verificar su identificación | id, name, email, phone, message, kind (info/tour), id_status, listing {id, slug, title}, source |
lead.updated | Cuando cambias la etapa de un lead en el portal | id, status |
listing.published | Al publicar una propiedad (portal o API) o reconfirmarla | id, slug, title, status |
listing.unpublished | Al despublicarla, retirarla, marcarla vendida/rentada o cuando el sistema la oculta (p. ej. sin reconfirmar) | id, slug, status (a veces title) |
listing.updated | El agente editó una propiedad publicada en el portal (no se envía por ediciones hechas vía API) | id, slug, title, status |
No se envían webhooks por los leads que tú creas con POST /leads ni por tus PUT de la API (evita bucles con CRMs que sincronizan en ambos sentidos).
Formato
POST /hooks/multilistado HTTP/1.1
Content-Type: application/json
X-PBM-Event: lead.created
X-PBM-Signature: sha256=5d41402abc4b2a76b9719d911017c592…
{"event":"lead.created","created_at":"2026-09-30T18:04:11.482Z","data":{"id":"6f1c…","name":"Ana López","email":"ana@example.com","phone":"+52 664 123 4567","message":"¿Sigue disponible?","kind":"info","id_status":null,"listing":{"id":"07e2…","slug":"casa-en-venta-playas-de-tijuana","title":"Casa en venta en Playas de Tijuana"},"source":"widget:3f9a0c1e2b4d5a6c7e8f9012"}}X-PBM-Signature = sha256= + HMAC-SHA256 en hexadecimal del cuerpo exacto (los bytes crudos) con tu secreto como clave. Verifícala antes de interpretar el JSON y compárala en tiempo constante.
Verificar la firma
// Node.js 18+ sin dependencias. MULTILISTADO_WEBHOOK_SECRET = el secreto del portal.
import http from 'node:http';
import crypto from 'node:crypto';
const SECRET = process.env.MULTILISTADO_WEBHOOK_SECRET;
function validSignature(rawBody, header) {
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex');
const a = Buffer.from(String(header || '')), b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
http.createServer((req, res) => {
const chunks = [];
req.on('data', c => chunks.push(c));
req.on('end', () => {
const raw = Buffer.concat(chunks); // bytes crudos: no uses JSON.stringify(req.body)
if (!validSignature(raw, req.headers['x-pbm-signature'])) { res.writeHead(401).end('bad signature'); return; }
const evt = JSON.parse(raw.toString('utf8'));
console.log(evt.event, evt.data.id); // guarda en tu CRM (en segundo plano)
res.writeHead(200).end('ok'); // responde rápido (menos de 10 s)
});
}).listen(process.env.PORT || 3000);<?php
// webhook.php — PHP 7.4+. MULTILISTADO_WEBHOOK_SECRET = el secreto del portal.
$secret = getenv('MULTILISTADO_WEBHOOK_SECRET');
$raw = file_get_contents('php://input'); // cuerpo crudo
$header = $_SERVER['HTTP_X_PBM_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);
if (!hash_equals($expected, $header)) {
http_response_code(401);
exit('bad signature');
}
$evt = json_decode($raw, true);
error_log($evt['event'] . ' ' . ($evt['data']['id'] ?? '')); // guarda en tu CRM
http_response_code(200);
echo 'ok';# Flask (pip install flask). MULTILISTADO_WEBHOOK_SECRET = el secreto del portal.
import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["MULTILISTADO_WEBHOOK_SECRET"].encode()
@app.post("/hooks/multilistado")
def multilistado_hook():
raw = request.get_data() # bytes crudos
expected = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-PBM-Signature", "")):
abort(401)
evt = request.get_json()
print(evt["event"], evt["data"].get("id")) # guarda en tu CRM
return "ok", 200Entrega y reintentos
- Un solo intento por evento, en cuanto ocurre. No hay reintentos automáticos: si tu servidor no responde, revisa Último estado en el portal y recupera lo perdido con
GET /api/v1/leadsoGET /api/v1/my/properties(API). - Cualquier respuesta 2xx cuenta como entregado. Tiempo máximo: 10 segundos. No se siguen redirecciones (usa la URL final). Solo se leen los primeros 64 KB de tu respuesta.
- Solo
https://hacia direcciones públicas; la URL se revisa al crearla y en cada envío. - Puede haber duplicados (p. ej. si publicas, despublicas y vuelves a publicar): identifica cada evento por
event+data.id+created_aty procesa de forma idempotente. - La firma no incluye marca de tiempo: descarta eventos con
created_atmuy antiguo si te preocupa la repetición.