Matchai Docs

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.

Próximamente. Esto se documenta antes del lanzamiento para que puedas planificar tu integración; todavía no está disponible.

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.

ParaLlamadaReferencia
Listar tus suscripcionesGET /webhook-subscriptionslistWebhookSubscriptions
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 activarlaPATCH /webhook-subscriptions/{webhook_subscription_id}updateWebhookSubscription
Dejar de enviar eventos a un endpointDELETE /webhook-subscriptions/{webhook_subscription_id}deleteWebhookSubscription
Emitir un nuevo secreto de firmaPOST /webhook-subscriptions/{webhook_subscription_id}/rotate-secretrotateWebhookSecret
Enviar ahora un evento de pruebaPOST /webhook-subscriptions/{webhook_subscription_id}/testtestWebhookSubscription
Ver los intentos de entregaGET /webhook-subscriptions/{webhook_subscription_id}/deliverieslistWebhookDeliveries
Volver a enviar una entrega fallidaPOST /webhook-subscriptions/{webhook_subscription_id}/deliveries/{delivery_id}/retryretryWebhookDelivery
Ponerte al día con eventos pasadosGET /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

EventoSe envía cuandodata.objectAlcance para recibirlo
lead.createdLlegó 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 contactocontacts:read
contact.createdCualquier contacto nuevo, sea cual sea su tipo. Un nuevo prospecto también envía lead.created.El contactocontacts:read
contact.updatedCambiaron los datos de un contacto o sus datos de prospecto. data.previous contiene los valores anteriores de los campos que cambiaron.El contactocontacts:read
contact.convertedUn prospecto se convirtió en cliente, o un cliente se convirtió en profesional. data.previous.type contiene el tipo anterior.El contactocontacts:read
contact.deletedSe eliminó un contacto.Un objeto eliminadocontacts:read

Conversaciones y mensajes

EventoSe envía cuandodata.objectAlcance para recibirlo
conversation.createdUna nueva conversación en la que participa la cuenta.La conversaciónmessages:read
message.receivedOtra persona escribió en una conversación en la que participa la cuenta.El mensajemessages:read
message.sentLa cuenta envió un mensaje, desde la app, el Asistente de Matchai o una app como la tuya.El mensajemessages:read

Citas

EventoSe envía cuandodata.objectAlcance para recibirlo
appointment.requestedAlguien le pidió un horario al titular de la cuenta. Queda a la espera de su respuesta.La citacalendar:read
appointment.bookedSe confirmó una cita: se reservó directamente, o se aceptó una solicitud.La citacalendar:read
appointment.rescheduledUna cita tiene un nuevo horario, o se propuso un nuevo horario (status: reschedule_pending).La citacalendar:read
appointment.cancelledSe canceló una cita o se rechazó una solicitud.La citacalendar:read

Anuncios

EventoSe envía cuandodata.objectAlcance para recibirlo
listing.createdUn nuevo anuncio en borrador, hecho a mano, con la creación con IA o por importación.El anunciolistings:read
listing.updatedCambiaron 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 anunciolistings:read
listing.publishedUn anuncio se publicó en la web pública.El anunciolistings:read
listing.unpublishedUn anuncio volvió a borrador.El anunciolistings:read
listing.archivedUn anuncio se retiró del mercado.El anunciolistings:read
listing.deletedSe eliminó un anuncio.Un objeto eliminadolistings:read
listing.savedAlguien 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_atlistings:read y contacts:read
listing.generation.completedUna ejecución de creación de un anuncio con IA terminó con éxito o falló.La generación del anunciolistings:read

Documentos, invitaciones y notificaciones

EventoSe envía cuandodata.objectAlcance para recibirlo
document.createdSe agregó un documento a Matchai Cloud, por carga, por correo electrónico o a través de un enlace de entrega.El documentodocuments:read
drop_link.upload_receivedAlguien subió un archivo a través de uno de los enlaces de entrega de la cuenta.El enlace de entrega y el documentodocuments:read
invitation.acceptedAlguien aceptó una invitación que envió la cuenta.La invitaciónteams:read o projects:read
notification.createdLa cuenta recibió una notificación.La notificaciónnotifications:read

Aprobaciones y pruebas

EventoSe envía cuandodata.objectAlcance para recibirlo
approval.resolvedUna acción final que pidió tu app se aprobó (y se realizó), se rechazó o caducó. data.object.result contiene el resultado.La aprobaciónNinguno. Se envía a la app que la pidió.
webhook.testLo enviaste con POST /webhook-subscriptions/{webhook_subscription_id}/test.Un mensaje fijowebhooks: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.

EncabezadoSignificado
Matchai-SignatureLa firma que debes verificar. Tiene este aspecto: t=1792177965,v1=5257a869…. Durante una rotación del secreto lleva dos valores v1.
Matchai-Event-IdEl mismo valor que el id del cuerpo. Úsalo para ignorar los duplicados.
Matchai-Delivery-AttemptEl 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=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

Toma 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_.

verify-signature.ts
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.

Test vector
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.

verify-signature.test.ts
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.

webhook-handler.ts
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. Compara created_at con lo que tienes, o vuelve a leer el objeto con un GET.
apply-event.ts
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

catch-up.ts
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ón https pública, y suscribe esa dirección.
  • Reproduce un evento real. Léelo con GET /events/{event_id}, o vuelve a enviar una entrega fallida con POST /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ó.

approvals.ts
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 https y responde con un 2xx en menos de 10 segundos.
  • Verificas Matchai-Signature sobre 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 id y 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 /events cubre cualquier hueco.

En esta página