WebhooksPróximamente
Recibe eventos firmados cuando llega un prospecto, se reserva una visita o cambia un anuncio. Catálogo, contenido, firma, reintentos, pruebas y registros.
Una suscripción de webhook le indica a Matchai adónde enviar los eventos. Cuando ocurre algo en una cuenta de Matchai conectada, como un nuevo prospecto, una visita reservada o un anuncio publicado, Matchai envía una solicitud POST firmada a tu endpoint con una descripción en JSON de lo que ocurrió.
Los webhooks reemplazan las consultas periódicas. Tu app se entera de un cambio en el momento en que ocurre, en lugar de preguntar una y otra vez, y la firma demuestra que el mensaje proviene de Matchai.
Cómo funciona
Suscríbete. Tu app llama a POST /webhook-subscriptions con la dirección https de tu endpoint y los eventos que quieres. La respuesta contiene el secreto de firma, que se muestra una sola vez.
Recibe. Cada vez que ocurre un evento que coincide, Matchai envía un POST a tu endpoint. El cuerpo es un Event en JSON. Lo acompañan tres encabezados: Matchai-Signature, Matchai-Event-Id y Matchai-Delivery-Attempt.
Verifica y responde. Tu endpoint comprueba la firma, responde con cualquier estado 2xx en menos de 10 segundos y hace el trabajo real después.
Reintentos. Una entrega que no recibe un 2xx se vuelve a enviar con esperas cada vez más largas, durante un máximo de 3 días.
Una suscripción pertenece a una conexión, es decir, a una cuenta de Matchai y a una app. Una app que atiende a tres profesionales tiene tres conexiones y crea suscripciones para cada una de ellas. Consulta Conectar con Matchai y Claves de API para saber cómo se crea una conexión.
Suscríbete a eventos
Para crear una suscripción se necesita el alcance webhooks:manage. Los webhooks están incluidos con la conexión y no gastan energía.
Campo
Tipo
Envía un encabezado Idempotency-Key con la solicitud. Un reintento con la misma clave dentro de las 24 horas devuelve la primera respuesta en lugar de crear una segunda suscripción.
curl https://api.matchai.co/v1/webhook-subscriptions \
-H "Authorization: Bearer $MATCHAI_API_KEY" \
-H "Idempotency-Key: 6f1d3c2a-0b7e-4f5a-9c8d-1e2f3a4b5c6d" \
-H "Content-Type: application/json" \
-d '{
"url": "https://maisonweb.example.com/hooks/matchai",
"events": ["lead.created", "appointment.booked"],
"description": "Leads and visits for camilletremblay.ca"
}'La respuesta es 201 Created. El secret aparece en la respuesta una vez y nunca más, así que guárdalo ahora.
{
"id": "8b9c0d1e-2f3a-4b4c-9d5e-6f7a8b9c0d1e",
"url": "https://maisonweb.example.com/hooks/matchai",
"description": "Leads and visits for camilletremblay.ca",
"events": ["lead.created", "appointment.booked"],
"status": "enabled",
"disabled_reason": null,
"created_at": "2026-10-10T18:05:00Z",
"updated_at": "2026-10-10T18:05:00Z",
"secret": "whsec_4fT9qLmZ2xVb7NcR8sKd3HwPj6YeA1uG"
}El secreto de firma empieza con whsec_. Guárdalo en tu almacén de secretos, nunca en el código ni en los registros. Si lo pierdes, rótalo.
Solo recibes eventos sobre datos que tus alcances te permiten leer. La última columna del catálogo indica el alcance que necesita cada evento, así que pide esos alcances cuando el titular de la cuenta conecte tu app.
Administra las suscripciones
Todas estas llamadas necesitan webhooks:manage.
| Para | Llamada | Referencia |
|---|---|---|
| Listar tus suscripciones | GET /webhook-subscriptions | listWebhookSubscriptions |
| Leer una suscripción (el secreto nunca se vuelve a devolver) | GET /webhook-subscriptions/{webhook_subscription_id} | getWebhookSubscription |
| Cambiar el endpoint o los eventos, o desactivarla o activarla | PATCH /webhook-subscriptions/{webhook_subscription_id} | updateWebhookSubscription |
| Dejar de enviar eventos a un endpoint | DELETE /webhook-subscriptions/{webhook_subscription_id} | deleteWebhookSubscription |
| Emitir un nuevo secreto de firma | POST /webhook-subscriptions/{webhook_subscription_id}/rotate-secret | rotateWebhookSecret |
| Enviar ahora un evento de prueba | POST /webhook-subscriptions/{webhook_subscription_id}/test | testWebhookSubscription |
| Ver los intentos de entrega | GET /webhook-subscriptions/{webhook_subscription_id}/deliveries | listWebhookDeliveries |
| Volver a enviar una entrega fallida | POST /webhook-subscriptions/{webhook_subscription_id}/deliveries/{delivery_id}/retry | retryWebhookDelivery |
| Ponerte al día con eventos pasados | GET /events y GET /events/{event_id} | listEvents, getEvent |
Eliminar una suscripción detiene los eventos para ese endpoint. No elimina nada en la cuenta de Matchai.
Una suscripción tiene estos campos:
Campo
Tipo
Catálogo de eventos
Estos son los tipos de evento, agrupados según su tema. Cada nombre enlaza a su página de referencia, que muestra el contenido del evento.
Pueden aparecer nuevos tipos de evento
Matchai puede agregar tipos de evento, y campos nuevos a los eventos existentes, sin una nueva versión de la API. Ignora cualquier tipo de evento que no conozcas y cualquier campo que no uses.
Contactos y prospectos
| Evento | Se envía cuando | data.object | Alcance para recibirlo |
|---|---|---|---|
lead.created | Llegó un nuevo prospecto a la cuenta: una consulta desde un anuncio, el sitio de Matchai, la Página Matchai o tu sitio web, una solicitud de visita, o alguien que guardó un anuncio. | El contacto | contacts:read |
contact.created | Cualquier contacto nuevo, sea cual sea su tipo. Un nuevo prospecto también envía lead.created. | El contacto | contacts:read |
contact.updated | Cambiaron los datos de un contacto o sus datos de prospecto. data.previous contiene los valores anteriores de los campos que cambiaron. | El contacto | contacts:read |
contact.converted | Un prospecto se convirtió en cliente, o un cliente se convirtió en profesional. data.previous.type contiene el tipo anterior. | El contacto | contacts:read |
contact.deleted | Se eliminó un contacto. | Un objeto eliminado | contacts:read |
Conversaciones y mensajes
| Evento | Se envía cuando | data.object | Alcance para recibirlo |
|---|---|---|---|
conversation.created | Una nueva conversación en la que participa la cuenta. | La conversación | messages:read |
message.received | Otra persona escribió en una conversación en la que participa la cuenta. | El mensaje | messages:read |
message.sent | La cuenta envió un mensaje, desde la app, el Asistente de Matchai o una app como la tuya. | El mensaje | messages:read |
Citas
| Evento | Se envía cuando | data.object | Alcance para recibirlo |
|---|---|---|---|
appointment.requested | Alguien le pidió un horario al titular de la cuenta. Queda a la espera de su respuesta. | La cita | calendar:read |
appointment.booked | Se confirmó una cita: se reservó directamente, o se aceptó una solicitud. | La cita | calendar:read |
appointment.rescheduled | Una cita tiene un nuevo horario, o se propuso un nuevo horario (status: reschedule_pending). | La cita | calendar:read |
appointment.cancelled | Se canceló una cita o se rechazó una solicitud. | La cita | calendar:read |
Anuncios
| Evento | Se envía cuando | data.object | Alcance para recibirlo |
|---|---|---|---|
listing.created | Un nuevo anuncio en borrador, hecho a mano, con la creación con IA o por importación. | El anuncio | listings:read |
listing.updated | Cambiaron los campos, las fotos o las habitaciones de un anuncio. data.previous contiene los valores anteriores de los campos de primer nivel que cambiaron. | El anuncio | listings:read |
listing.published | Un anuncio se publicó en la web pública. | El anuncio | listings:read |
listing.unpublished | Un anuncio volvió a borrador. | El anuncio | listings:read |
listing.archived | Un anuncio se retiró del mercado. | El anuncio | listings:read |
listing.deleted | Se eliminó un anuncio. | Un objeto eliminado | listings:read |
listing.saved | Alguien guardó uno de los anuncios de la cuenta. data.object.contact es tu registro de contacto de esa persona. | El anuncio, el contacto y saved_at | listings:read y contacts:read |
listing.generation.completed | Una ejecución de creación de un anuncio con IA terminó con éxito o falló. | La generación del anuncio | listings:read |
Documentos, invitaciones y notificaciones
| Evento | Se envía cuando | data.object | Alcance para recibirlo |
|---|---|---|---|
document.created | Se agregó un documento a Matchai Cloud, por carga, por correo electrónico o a través de un enlace de entrega. | El documento | documents:read |
drop_link.upload_received | Alguien subió un archivo a través de uno de los enlaces de entrega de la cuenta. | El enlace de entrega y el documento | documents:read |
invitation.accepted | Alguien aceptó una invitación que envió la cuenta. | La invitación | teams:read o projects:read |
notification.created | La cuenta recibió una notificación. | La notificación | notifications:read |
Aprobaciones y pruebas
| Evento | Se envía cuando | data.object | Alcance para recibirlo |
|---|---|---|---|
approval.resolved | Una acción final que pidió tu app se aprobó (y se realizó), se rechazó o caducó. data.object.result contiene el resultado. | La aprobación | Ninguno. Se envía a la app que la pidió. |
webhook.test | Lo enviaste con POST /webhook-subscriptions/{webhook_subscription_id}/test. | Un mensaje fijo | webhooks:manage |
Un objeto eliminado es { "id": "…", "object": "listing", "deleted": true }, donde object es contact o listing.
Cómo es una entrega
Cada entrega es un POST con un cuerpo JSON y estos encabezados.
| Encabezado | Significado |
|---|---|
Matchai-Signature | La firma que debes verificar. Tiene este aspecto: t=1792177965,v1=5257a869…. Durante una rotación del secreto lleva dos valores v1. |
Matchai-Event-Id | El mismo valor que el id del cuerpo. Úsalo para ignorar los duplicados. |
Matchai-Delivery-Attempt | El número de intento, a partir de 1. |
El cuerpo es el mismo objeto Event para todos los tipos. GET /events devuelve los mismos objetos.
Campo
Tipo
Una entrega de lead.created, abreviada a los campos que usan la mayoría de las apps:
{
"id": "0d1e2f3a-4b5c-4d6e-9f7a-8b9c0d1e2f3a",
"type": "lead.created",
"created_at": "2026-10-16T19:12:45Z",
"api_version": "v1",
"test_mode": false,
"account": {
"user_id": "3f1c9a2e-7b44-4d0e-9c1a-5e8f2b6d4a10",
"professional_id": "8a2d4f6b-1c3e-4a5b-9d7f-0e2c4b6a8d1f"
},
"data": {
"object": {
"id": "c5d8e1f2-3a4b-4c6d-9e8f-7a6b5c4d3e2f",
"type": "lead",
"first_name": "Julien",
"last_name": "Bouchard",
"display_name": "Julien Bouchard",
"email": "julien.bouchard@example.com",
"phone": "+15145550142",
"notes": "Looking for a 2-bedroom on the Plateau, close to the orange line.",
"source": "listing",
"source_listing_id": "b7e4c2a1-9f3d-4e6b-8a5c-1d2f3e4a5b6c",
"conversation_id": "d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a",
"created_at": "2026-10-16T19:12:44Z",
"updated_at": "2026-10-17T16:20:00Z"
}
}
}Los nombres de los campos están en snake_case, los id son cadenas opacas y un evento solo lleva datos que tus alcances te permiten leer.
Verifica la firma
Cualquiera puede enviar una solicitud a tu endpoint. Verifica Matchai-Signature en cada entrega antes de confiar en el cuerpo.
El encabezado tiene este aspecto:
Matchai-Signature: t=1792177965,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdToma t (segundos Unix) y el cuerpo sin procesar de la solicitud, exactamente como se recibió, antes de cualquier análisis del JSON.
Calcula el HMAC-SHA256 de <t>.<raw body> con el secreto de firma de tu suscripción (whsec_…), en hexadecimal en minúsculas. El texto firmado es la marca de tiempo, un punto y luego el cuerpo.
Compara el resultado con cada valor v1 en tiempo constante. Acepta la entrega si alguno de ellos coincide.
Rechaza la entrega si t difiere en más de 5 minutos de tu reloj. Así evitas que alguien reenvíe una solicitud antigua.
Usa el cuerpo sin procesar
Firma y compara los bytes que envió Matchai. Un cuerpo que se analizó y se volvió a escribir puede diferir en los espacios o en el orden de las claves, y entonces ninguna firma coincidirá. En la mayoría de los frameworks web, esto significa leer el cuerpo como texto o como bytes antes de que se ejecute cualquier middleware de JSON.
Usa el secreto exactamente como lo recibiste, incluido el prefijo whsec_.
import { createHmac, timingSafeEqual } from 'node:crypto';
const TOLERANCE_SECONDS = 5 * 60;
/**
* Checks a Matchai-Signature header against the raw request body.
* Returns true only when the timestamp is fresh and one v1 signature matches.
*/
export function verifyMatchaiSignature(
rawBody: string,
header: string | null | undefined,
secret: string,
nowSeconds: number = Math.floor(Date.now() / 1000),
): boolean {
if (!header) return false;
// Header: t=1792177965,v1=<hex>[,v1=<hex>]
let timestamp = '';
const signatures: string[] = [];
for (const part of header.split(',')) {
const separator = part.indexOf('=');
if (separator === -1) continue;
const name = part.slice(0, separator).trim();
const value = part.slice(separator + 1).trim();
if (name === 't') timestamp = value;
else if (name === 'v1') signatures.push(value);
}
const sentAt = Number(timestamp);
if (!Number.isInteger(sentAt) || signatures.length === 0) return false;
// Reject a timestamp more than 5 minutes from your clock. This stops replays.
if (Math.abs(nowSeconds - sentAt) > TOLERANCE_SECONDS) return false;
// HMAC-SHA256 of "<t>.<raw body>" with the signing secret, as lowercase hex.
const expected = Buffer.from(
createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex'),
);
// Compare with every v1 value in constant time. One match is enough.
return signatures.some((candidate) => {
const received = Buffer.from(candidate);
return received.length === expected.length && timingSafeEqual(received, expected);
});
}Comprueba tu código con una firma conocida
Este ejemplo es inventado. Úsalo para comprobar que tu código calcula la firma igual que Matchai, antes de apuntarlo a una suscripción real.
secret: whsec_test_0123456789abcdef
header: t=1792177965,v1=f222c924e7438518b6cec8b46239f468624169721a58570efc3084921aa229c1
body: {"id":"0d1e2f3a-4b5c-4d6e-9f7a-8b9c0d1e2f3a","type":"webhook.test","created_at":"2026-10-16T19:12:45Z","api_version":"v1","test_mode":true,"account":{"user_id":"3f1c9a2e-7b44-4d0e-9c1a-5e8f2b6d4a10","professional_id":null},"data":{"object":{"message":"This is a test event from Matchai."}}}La muestra es antigua, así que pasa su propia marca de tiempo como hora actual. Así, si la comprobación falla, no será por la ventana de 5 minutos.
import assert from 'node:assert/strict';
import { verifyMatchaiSignature } from './verify-signature.js';
const secret = 'whsec_test_0123456789abcdef';
const header =
't=1792177965,v1=f222c924e7438518b6cec8b46239f468624169721a58570efc3084921aa229c1';
const body =
'{"id":"0d1e2f3a-4b5c-4d6e-9f7a-8b9c0d1e2f3a","type":"webhook.test","created_at":"2026-10-16T19:12:45Z","api_version":"v1","test_mode":true,"account":{"user_id":"3f1c9a2e-7b44-4d0e-9c1a-5e8f2b6d4a10","professional_id":null},"data":{"object":{"message":"This is a test event from Matchai."}}}';
assert.equal(verifyMatchaiSignature(body, header, secret, 1792177965), true);
assert.equal(verifyMatchaiSignature(`${body} `, header, secret, 1792177965), false); // body changed
assert.equal(verifyMatchaiSignature(body, header, secret, 1792177965 + 301), false); // too old
console.log('The signature check works.');Recibe una entrega
Responde con cualquier estado 2xx en menos de 10 segundos. Cualquier otra respuesta, o la falta de respuesta a tiempo, cuenta como una entrega fallida y se reintenta. Así que verifica, guarda o pon en cola el evento, responde, y haz el trabajo lento después.
Un controlador para cualquier entorno de ejecución que use los objetos estándar Request y Response.
import { verifyMatchaiSignature } from './verify-signature.js';
type MatchaiEvent = {
id: string;
type: string;
created_at: string;
api_version: 'v1';
test_mode: boolean;
account: { user_id: string; professional_id: string | null };
data: { object: Record<string, unknown>; previous?: Record<string, unknown> };
};
const secret = process.env.MATCHAI_WEBHOOK_SECRET ?? '';
// Use a database with a unique key on the event id in production.
const seenEventIds = new Set<string>();
export async function handleMatchaiWebhook(request: Request): Promise<Response> {
// Read the body as text first. Do not parse JSON before you verify.
const rawBody = await request.text();
if (!verifyMatchaiSignature(rawBody, request.headers.get('Matchai-Signature'), secret)) {
return new Response('Invalid signature', { status: 400 });
}
const event = JSON.parse(rawBody) as MatchaiEvent;
// A delivery can arrive more than once. Acknowledge duplicates and do nothing.
if (seenEventIds.has(event.id)) return new Response(null, { status: 200 });
seenEventIds.add(event.id);
// Answer now and do the slow work afterwards. In production, put the event on a queue.
void processEvent(event).catch((error) => console.error('Event failed', event.id, error));
return new Response(null, { status: 200 });
}
async function processEvent(event: MatchaiEvent): Promise<void> {
switch (event.type) {
case 'lead.created':
console.log('New lead', event.data.object.id);
break;
case 'appointment.booked':
console.log('Visit booked', event.data.object.id);
break;
default:
// Ignore event types you do not know. New ones can appear at any time.
break;
}
}Si una app atiende a varias cuentas, dale a cada suscripción su propia dirección, por ejemplo /hooks/matchai/{your broker id}. Cada suscripción tiene su propio secreto, y la dirección te indica con qué secreto verificar antes de leer el cuerpo.
Reintentos y fallos
Una entrega que no recibe un 2xx se reintenta con esperas cada vez más largas: aproximadamente 1 minuto, 5 minutos, 30 minutos, 2 horas y 6 horas, y luego cada 12 horas, durante un máximo de 3 días. Matchai-Delivery-Attempt te indica qué intento estás viendo.
Si todas las entregas a una suscripción fallan durante 3 días, Matchai desactiva la suscripción. Muestra disabled_reason: failing, y se le avisa por correo electrónico al desarrollador de la app.
Reactivarla con PATCH /webhook-subscriptions/{webhook_subscription_id} y { "status": "enabled" } reanuda las entregas a partir de ese momento. Los eventos del tiempo en que estuvo desactivada no se vuelven a enviar, así que ponte al día con GET /events.
Cuando el titular de la cuenta desconecta tu app, sus suscripciones se detienen y muestran disabled_reason: connection_ended. Cuando el titular de la cuenta vuelva a conectar tu app, lista tus suscripciones y crea o reactiva las que necesites.
Orden y duplicados
Los eventos pueden llegar desordenados y más de una vez. Escribe tu controlador de modo que eso no importe.
- Ignora los duplicados con
id. Conserva los id que ya procesaste durante al menos los 3 días en que Matchai sigue reintentando. - Decide qué es lo actual con
created_at, o vuelve a preguntar. Un evento más antiguo del mismo objeto puede llegar después de uno más reciente. Comparacreated_atcon lo que tienes, o vuelve a leer el objeto con unGET.
type StoredObject = { lastEventAt: number };
// Your own storage, keyed by the object's id.
const store = new Map<string, StoredObject>();
/** Returns true when the event is newer than what you hold, so you should apply it. */
export function isNewer(objectId: string, eventCreatedAt: string): boolean {
const eventAt = Date.parse(eventCreatedAt);
const current = store.get(objectId);
if (current && current.lastEventAt >= eventAt) return false; // a late, older event
store.set(objectId, { lastEventAt: eventAt });
return true;
}Para todo lo que deba ser exacto, trata el evento como una señal y lee el objeto desde la API. data.object también está disponible en GET /events/{event_id}.
Ponte al día después de una interrupción
GET /events conserva 30 días de eventos de la conexión, primero los más recientes, con la misma estructura que las entregas. Úsalo después de una interrupción del servicio, después de reactivar una suscripción o de forma programada como red de seguridad. Necesita webhooks:manage.
Campo
Tipo
const api = 'https://api.matchai.co/v1';
export async function eventsSince(since: string, token: string) {
const events: Array<{ id: string; type: string; created_at: string }> = [];
let cursor: string | null = null;
do {
const url = new URL(`${api}/events`);
url.searchParams.set('since', since);
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
if (!response.ok) throw new Error(`Matchai answered ${response.status}`);
const page = await response.json();
events.push(...page.data);
cursor = page.next_cursor;
} while (cursor);
// The list is newest first. Replay oldest first, skipping ids you already handled.
return events.reverse();
}Prueba tu endpoint
POST /webhook-subscriptions/{webhook_subscription_id}/test envía de inmediato un evento webhook.test al endpoint y devuelve la entrega. El evento tiene test_mode: true, y data.object.message es un texto fijo. Devuelve un 2xx para superar la prueba.
curl -X POST https://api.matchai.co/v1/webhook-subscriptions/8b9c0d1e-2f3a-4b4c-9d5e-6f7a8b9c0d1e/test \
-H "Authorization: Bearer $MATCHAI_API_KEY" \
-H "Idempotency-Key: 2c7d1a40-6e3b-4f8a-b9d2-0a1b2c3d4e5f"{
"id": "9c0d1e2f-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
"event_id": "0d1e2f3a-4b5c-4d6e-9f7a-8b9c0d1e2f3a",
"event_type": "webhook.test",
"attempt": 1,
"status": "succeeded",
"response_status": 200,
"error": null,
"created_at": "2026-10-16T19:12:46Z",
"next_attempt_at": null
}Otras formas de probar:
- Usa el modo de prueba. Una app que está conectada solo a la cuenta de Matchai de su propio desarrollador está en modo de prueba y no se factura. Sus eventos llevan
test_mode: true, así que puedes crear y comprobar todo el flujo primero con tu propia cuenta. - Haz accesible tu máquina. Tu endpoint debe usar
https. Mientras desarrollas, usa un túnel que le dé a tu servidor local una direcciónhttpspública, y suscribe esa dirección. - Reproduce un evento real. Léelo con
GET /events/{event_id}, o vuelve a enviar una entrega fallida conPOST /webhook-subscriptions/{webhook_subscription_id}/deliveries/{delivery_id}/retry.
Rota el secreto de firma
Rota un secreto de forma periódica, o de inmediato si pudo haberse filtrado. La rotación nunca hace que se pierda un evento, porque el secreto anterior sigue funcionando durante 24 horas.
Llama a POST /webhook-subscriptions/{webhook_subscription_id}/rotate-secret. La respuesta es la suscripción con un nuevo secret, que se muestra una sola vez.
Durante las 24 horas siguientes, cada entrega lleva en Matchai-Signature un valor v1 por cada secreto.
Despliega el nuevo secreto. El código de verificación de arriba acepta una entrega cuando coincide cualquier valor v1, así que funciona con el secreto anterior antes del despliegue y con el nuevo después.
Pasadas las 24 horas, solo el nuevo secreto firma las entregas.
Registros de entrega
GET /webhook-subscriptions/{webhook_subscription_id}/deliveries enumera los intentos de entrega recientes de un endpoint, primero los más recientes, y los conserva durante 30 días. Usa status (pending, succeeded o failed) para acotar la lista. Necesita webhooks:manage.
{
"data": [
{
"id": "9c0d1e2f-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
"event_id": "0d1e2f3a-4b5c-4d6e-9f7a-8b9c0d1e2f3a",
"event_type": "lead.created",
"attempt": 2,
"status": "failed",
"response_status": 503,
"error": null,
"created_at": "2026-10-16T19:13:46Z",
"next_attempt_at": "2026-10-16T19:18:46Z"
}
],
"next_cursor": null
}Campo
Tipo
Para volver a enviar ahora mismo una entrega fallida, llama a POST /webhook-subscriptions/{webhook_subscription_id}/deliveries/{delivery_id}/retry. Devuelve una nueva entrega.
Acciones finales y aprobaciones
Cuando tu conexión está configurada en Preguntarme, una acción final, como enviar un mensaje o publicar un anuncio, devuelve 202 Accepted con un approval_id. No ocurre nada hasta que el titular de la cuenta la aprueba en Matchai.
Luego Matchai envía approval.resolved a tu app, tenga los alcances que tenga. data.object.id es el approval_id del 202. status indica si la acción se aprobó, se rechazó o caducó. Cuando se aprobó, result.status_code y result.body son lo que devolvió la llamada cuando Matchai la realizó.
type Approval = {
id: string;
status: 'pending' | 'approved' | 'declined' | 'expired' | 'cancelled';
summary: string;
result: { status_code: number; body: Record<string, unknown> } | null;
};
export function onApprovalResolved(approval: Approval): void {
if (approval.status === 'approved') {
console.log('Done:', approval.summary, approval.result?.status_code);
} else {
console.log('Not done:', approval.summary, approval.status);
}
}Si no puedes recibir webhooks, consulta periódicamente GET /approvals/{approval_id} en su lugar. Más información en getApproval.
Antes de pasar a producción
- Tu endpoint usa
httpsy responde con un2xxen menos de 10 segundos. - Verificas
Matchai-Signaturesobre el cuerpo sin procesar, comparas en tiempo constante y rechazas una marca de tiempo que difiera en más de 5 minutos. - Ignoras los duplicados por
idy toleras los eventos desordenados. - Ignoras los tipos de evento y los campos que no conoces.
- El secreto de firma está en un almacén de secretos, y sabes cómo rotarlo.
- Una llamada programada a
GET /eventscubre cualquier hueco.
Creadores de sitios web
Usa los webhooks con un feed de anuncios, la recepción de prospectos y las solicitudes de visita.
Conectar con Matchai
Cómo obtiene una app una conexión a la cuenta de un profesional.
Errores
Los códigos de error y cómo manejarlos.
Límites de solicitudes
Los límites por conexión, y qué hacer ante un 429.
Páginas legibles por IA
Cómo cada sitio público de Matchai se describe ante asistentes de IA y rastreadores, de llms.txt a copias en Markdown y JSON-LD. Replícalo en tu propio sitio.
Creadores de sitios web
Muestra los anuncios de Matchai de un corredor en los sitios que creas, hoy sin clave. El kit para socios (feeds, prospectos, visitas, webhooks) llega pronto.