Docs

Webhooks

Última actualización: 2026-10-036 min de lectura

Un webhook es un POST que GREM envía a su URL cuando ocurre algo: termina un trabajo, cambia un inmueble o un lead. Gestionar suscripciones requiere el ámbito webhooks y es gratuito. Los webhooks de una agencia se gestionan con la clave de toda la agencia; una clave de agente recibe 403.

Suscribirse

curl -X POST https://developers.grem.capital/api/v1/webhooks \
  -H "Authorization: Bearer gsk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/grem-hook","events":["content.completed","content.failed","lead.created"]}'
{ "id": "...", "url": "https://example.com/grem-hook", "events": [...], "status": "active",
  "failureCount": 0, "lastDeliveryAt": null, "secret": "whsec_..." }
  • url: una dirección pública https:// en el puerto estándar (443), sin user:password. Otros puertos, credenciales en la dirección y las direcciones privadas, locales e internas se rechazan (400). La regla del puerto se aplica al fijar una dirección: las direcciones guardadas antes con otro puerto siguen recibiendo entregas.
  • events: al menos un evento de la lista de abajo; los nombres desconocidos se descartan.
  • secret: el secreto de firma. Se devuelve una sola vez: guárdelo.
  • Hasta 10 direcciones por cuenta y, aparte, hasta 10 direcciones de sandbox creadas con claves de prueba; la 11.ª se rechaza con 422 business_rule.
  • Las direcciones creadas con una clave de prueba son direcciones del sandbox: reciben solo eventos de trabajos del sandbox (Sandbox).

Las suscripciones también se gestionan en el panel: API → Webhooks. Allí también puede cambiar el secreto de firma.

Gestión

SolicitudQué hace
GET /webhookssus suscripciones (sin secretos)
PATCH /webhooks/{id}cambiar url, events o status (active / disabled)
DELETE /webhooks/{id}eliminar la suscripción
POST /webhooks/{id}/testenviar un evento de prueba; el cuerpo {"eventType": "content.completed"} define la forma
GET /webhooks/{id}/deliverieslas últimas 50 entregas: estado, código de respuesta, latencia, inicio de la respuesta
POST /webhooks/{id}/deliveries/{deliveryId}/resendreenviar una entrega; deliveryId es el _id de una fila del registro

PATCH sin ningún evento válido en events o con un status distinto de active / disabled se rechaza con 400. DELETE de una suscripción desconocida responde 404. En el registro de entregas, payload es el data del evento.

Eventos

Trabajos terminados

EventoCuándo
valuation.completed, valuation.failedinforme de valoración completo
review.completed, review.failedrevisión de informe
content.completed, content.partial, content.failedpaquete de contenido (partial: algunos archivos fallaron)
text.completed, text.failedtextos
video.completed, video.failedvídeo
avatar.completed, avatar.failedvídeo con avatar
photo.completed, photo.failedmejora de fotos
floorplan.completed, floorplan.failedplano
pdf.completed, pdf.failedpresentación en PDF

data: { "jobId", "status", "state" }. status es el estado original de la herramienta en mayúsculas (por ejemplo, COMPLETED); decida según state. Si un trabajo no ha terminado en 30 minutos, .failed con "reason": "timeout". Un trabajo se sigue solo si al crearlo ya existía una suscripción a su evento (Trabajos y resultados).

Inmuebles

EventoCuándo
object.createdha creado un inmueble
object.updatedun inmueble ha cambiado, también por un moderador
object.publishedun inmueble se ha hecho público
object.deletedse ha eliminado un borrador o archivado un inmueble publicado

data: { "objectId", "kind", "status", "occurredAt" }; en object.deleted, en lugar de status va mode (deleted o archived). Los datos completos: GET /objects/{kind}/{id}.

Leads

EventoCuándo
lead.createdun lead nuevo para usted
lead.updatedha cambiado el estado o el responsable

data: { "leadId", "status", "type", "source", "objectId", "occurredAt" }. El evento no contiene datos personales del cliente: el lead se lee con GET /leads/{id}.

Swipe Match

EventoCuándo
match.lead.offeredse le ha ofrecido un lead (a usted o a un agente de su agencia)
match.lead.offer_closedla oferta se ha cerrado sin usted (tiempo agotado)
match.lead.matchedel lead se le ha asignado
match.lead.updatedel lead ha pasado a una nueva etapa
match.lead.unassignedel lead se le ha retirado

data: { "leadId", "eventId", "eventCode", "occurredAt", "source", "stage", "active", "leadKind", "clientType", "country", "brokerId", "agencyId" }. El nombre, el teléfono y el correo del cliente nunca van en los eventos (Swipe Match).

Formato de entrega

POST https://example.com/grem-hook
Content-Type: application/json
x-grem-event: content.completed
x-grem-signature: t=1759400000,v1=3b78a03b78058d229023db2a0b4ee072...

{ "id": "...", "type": "content.completed", "createdAt": "2026-10-02T10:00:00.000Z", "data": { ... } }
  • id es igual en todos los reintentos de una entrega: úselo para no procesar un evento dos veces.
  • t y createdAt son el momento del intento concreto.
  • Una entrega de prueba (POST /webhooks/{id}/test) con un eventType de la lista lleva test: true en data; sin él, el tipo de evento es webhook.test. Un eventType que no está en la lista es 400. webhook.test en sí se acepta.

Verificar la firma

La firma es un HMAC-SHA256 de la cadena "<t>.<cuerpo sin procesar>" con el secreto de la suscripción, en hexadecimal en v1. Verifíquela antes de cualquier procesamiento y rechace los t antiguos (protección contra reenvíos).

Calcule la firma sobre el cuerpo sin procesar, tal como llegó, antes de analizar el JSON. Volver a serializarlo cambia los bytes y la firma no coincidirá.

const crypto = require('crypto');

function verifyGremSignature(secret, header, rawBody, toleranceSec = 300) {
  const parts = Object.fromEntries(String(header || '').split(',').map((kv) => kv.split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 || '');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post('/grem-hook', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verifyGremSignature(process.env.GREM_WEBHOOK_SECRET, req.get('x-grem-signature'), raw)) {
    return res.status(400).send('invalid signature');
  }
  const event = JSON.parse(raw);
  // event.id, event.type, event.data
  res.sendStatus(200);
});

En el SDK lo hace verifyWebhookSignature().

Reintentos y desactivación

  • Una entrega es correcta si su servidor responde 2xx en 10 segundos. Responda rápido y haga el trabajo pesado en segundo plano.
  • Una entrega fallida se reintenta a los 5 minutos, 30 minutos, 2 horas y 6 horas: hasta 5 intentos en total.
  • Tras 5 entregas seguidas totalmente fallidas, la suscripción se desactiva (status: disabled). Reactívela con PATCH /webhooks/{id} y {"status":"active"} o en el panel.
  • El registro de entregas (GET /webhooks/{id}/deliveries) muestra el código de respuesta de su servidor y el inicio del cuerpo de la respuesta: así verá por qué falló una entrega.

Artículos relacionados

¿Le resultó útil este artículo?