Set up
- Go to Portal → More → Webhooks.
- Enter your receiver URL: it must be
https://on a public server (private IPs andlocalhostare refused). - Tick the Events you want and click Create.
- Copy the secret shown ("Webhook created. Secret (save it): …"): it is shown only once and is used to verify the signature.
- Click Test to send a test
lead.created. The Last status column showsok,http 4xx/http 5xxorerror: delivery failedwith the time.
Webhooks are per agent: you receive events for your listings and for leads assigned to you.
Free for licensed agents and agencies. Create your account, verify your license and generate your API key or widget in minutes.
Events
| Event | When it is sent | data |
|---|---|---|
lead.created | When a new lead is notified to you: forms on multilistado.mx, your Multilistado site, the widget and collections. Showing requests are sent once the buyer finishes verifying their ID | id, name, email, phone, message, kind (info/tour), id_status, listing {id, slug, title}, source |
lead.updated | When you change a lead's stage in the portal | id, status |
listing.published | When a listing is published (portal or API) or reconfirmed | id, slug, title, status |
listing.unpublished | When it is unpublished, withdrawn, marked sold/rented or hidden by the system (e.g. not reconfirmed) | id, slug, status (sometimes title) |
listing.updated | The agent edited a published listing in the portal (not sent for edits made via the API) | id, slug, title, status |
No webhooks are sent for leads you create with POST /leads or for your API PUTs (this avoids loops with CRMs that sync both ways).
Payload
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":"Is it still available?","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= + the hex HMAC-SHA256 of the exact body (raw bytes) keyed with your secret. Verify it before parsing the JSON and compare in constant time.
Verify the signature
// Node.js 18+, no dependencies. MULTILISTADO_WEBHOOK_SECRET = the secret from the 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); // raw bytes: do not use 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); // store it in your CRM (in the background)
res.writeHead(200).end('ok'); // answer fast (under 10 s)
});
}).listen(process.env.PORT || 3000);<?php
// webhook.php — PHP 7.4+. MULTILISTADO_WEBHOOK_SECRET = the secret from the portal.
$secret = getenv('MULTILISTADO_WEBHOOK_SECRET');
$raw = file_get_contents('php://input'); // raw body
$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'] ?? '')); // store it in your CRM
http_response_code(200);
echo 'ok';# Flask (pip install flask). MULTILISTADO_WEBHOOK_SECRET = the secret from the 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() # raw bytes
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")) # store it in your CRM
return "ok", 200Delivery and retries
- One attempt per event, as soon as it happens. There are no automatic retries: if your server is down, check Last status in the portal and recover what you missed with
GET /api/v1/leadsorGET /api/v1/my/properties(API). - Any 2xx response counts as delivered. Timeout: 10 seconds. Redirects are not followed (use the final URL). Only the first 64 KB of your response are read.
https://to public addresses only; the URL is checked when created and on every delivery.- Duplicates can happen (e.g. publish, unpublish, publish again): identify each event by
event+data.id+created_atand process idempotently. - The signature has no timestamp: drop events whose
created_atis too old if replay concerns you.