quore/Documentación de la API

Webhooks

Recibe en tu sistema un aviso cada vez que se cotiza, se emite o se cobra un producto, firmado para que puedas verificar que viene de Quore.

Configuración

  • Se configuran por producto: una URL para pólizas y otra para cotizaciones, cada una con su secreto de firma.
  • Como todo el producto, aplican desde que se publica la versión.
  • Se envían para todos los canales: API, widget, portal de intermediarios y portal de clientes.

Eventos

EventoURLCuándo se envía
policy.createdPólizasAl emitirse una póliza, por cualquier canal.
policy.status_changedPólizasCuando una póliza pendiente cambia de estado: pending_payment (condiciones aprobadas, con payment_url para pagar), active o rejected, al resolverse sus condiciones, al confirmarse su pago o al vencer.
payment.succeededPólizasAl confirmarse el pago único online de una póliza (Stripe, Mercado Pago o Rebill).
payment.failedPólizasAl rechazarse un intento de cobro de una póliza pendiente de pago: pago único, primer período de un producto recurrente o validación de tarjeta de uno por uso. Uno por intento (los eventos repetidos del proveedor para el mismo intento no se reenvían) e incluye failure_reason.
payment.refund_requiredPólizasSolo pólizas antiguas pagadas antes de resolver sus condiciones: al rechazarse ya pagadas (o pagarse ya rechazadas) requieren un reembolso manual.
payment.refundedPólizasAl marcar un administrador ese reembolso como hecho.
quote.createdCotizacionesAl calcularse una cotización.
quote.pending_approvalCotizacionesAl calcularse una cotización que requiere aprobación.
quote.approvedCotizacionesAl aprobarse una cotización pendiente.
quote.rejectedCotizacionesAl rechazarse una cotización pendiente (incluye reason).

Contenido

Todos los eventos se envían como POST con JSON: event, timestamp (ISO 8601) y el registro (policy o quote; los de pago agregan payment). policy.created lleva la póliza completa:

policy.created

{
  "event": "policy.created",
  "timestamp": "2026-10-26T14:03:11.512Z",
  "policy": {
    "id": "b2a7…",
    "policy_number_internal": "POL-0042",
    "policy_number_insurer": null,
    "status": "active",
    "product_id": "PRODUCT_ID",
    "coverage_id": "basica",
    "quote_id": "QUOTE_ID",
    "data": {
      "insured": {
        "nombre": "Ana Pérez"
      },
      "payment": {
        "method": "credit_card",
        "card_last4": "4242",
        "card_brand": "visa",
        "card_holder": "Ana Pérez"
      }
    },
    "conditions": [],
    "net_premium": "42000",
    "total_premium": "50820",
    "currency": "ARS",
    "tax_breakdown": [
      {
        "id": "iva",
        "name": "IVA",
        "amount": 8820
      }
    ],
    "installments": 12,
    "installment_amount": "4235",
    "commissions": {
      "commission": {
        "tierId": "TIER_ID",
        "tierName": "Productor",
        "type": "percentage",
        "rate": 0.1,
        "amount": 4200,
        "affectsPricing": false
      },
      "ancestorCommissions": []
    },
    "environment": "live",
    "payment_method_status": null,
    "operator_id": "USER_ID",
    "start_date": "2026-10-26T00:00:00.000Z",
    "end_date": "2027-10-26T00:00:00.000Z",
    "created_at": "2026-10-26T14:03:11.402Z",
    "updated_at": "2026-10-26T14:03:11.402Z"
  }
}
CampoContenido
dataDatos de emisión por grupo (insured, asset, coverage, general) y el medio de pago enmascarado (payment: method, card_last4, card_brand, card_holder, provider).
net_premiumPrima (sin impuestos).
total_premiumPremio (total a pagar: prima, impuestos y recargo financiero).
tax_breakdownImpuestos aplicados: lista de { id, name, amount }.
commissionsComisiones calculadas al cotizar, o null: commission (tierId, tierName, type, rate, amount, affectsPricing) y ancestorCommissions (misma forma más intermediaryId y levelName, del superior más cercano al más lejano).
operator_idUsuario que emitió (intermediario u operador), o null si se emitió por API o widget público.
payment_method_statusCobro recurrente o por uso: pending hasta que el asegurado registra su medio de pago, luego active; null si no aplica. La póliza se activa con el primer cobro confirmado (recurrente) o la tarjeta validada (por uso).
environmentlive o test.

En policy.created los montos (net_premium, total_premium, installment_amount) llegan como texto decimal (por ejemplo "42000"); conviértelos a número al procesarlos.

El número completo de tarjeta nunca se envía, salvo que el producto active de forma explícita el reenvío de tarjeta en modo de pago manual: en ese caso llega solo en policy.created, aparte, en payment_pan.card_number, y no se guarda. El CVV nunca se pide, guarda ni envía.

policy.created con reenvío de tarjeta activado

{
  "event": "policy.created",
  "timestamp": "2026-10-26T14:03:11.512Z",
  "policy": {
    "id": "b2a7…",
    "…": "…"
  },
  "payment_pan": {
    "card_number": "4242424242424242"
  }
}

policy.status_changed

{
  "event": "policy.status_changed",
  "timestamp": "2026-10-27T09:12:40.010Z",
  "policy": {
    "id": "b2a7…",
    "policy_number_internal": "POL-0043",
    "status": "active",
    "conditions": [
      {
        "id": "inspection",
        "type": "inspection",
        "label": "Inspección",
        "status": "approved"
      }
    ],
    "payment_status": "paid",
    "pending_reason": null,
    "refund_status": null,
    "updated_at": "2026-10-27T09:12:39.981Z"
  }
}

payment.succeeded

{
  "event": "payment.succeeded",
  "timestamp": "2026-10-26T14:05:02.300Z",
  "policy": {
    "id": "b2a7…",
    "policy_number_internal": "POL-0044",
    "status": "active",
    "environment": "live",
    "payment_method_status": null
  },
  "payment": {
    "id": "PAYMENT_ID",
    "provider": "mercadopago",
    "status": "paid",
    "amount": 50820,
    "currency": "ARS",
    "provider_ref": "1234567890"
  }
}

payment.failed

{
  "event": "payment.failed",
  "timestamp": "2026-10-26T14:04:40.120Z",
  "policy": {
    "id": "b2a7…",
    "policy_number_internal": "POL-0045",
    "status": "payment_failed",
    "environment": "live",
    "payment_method_status": null
  },
  "payment": {
    "id": "PAYMENT_ID",
    "provider": "stripe",
    "status": "failed",
    "amount": 50820,
    "currency": "ARS",
    "provider_ref": "pi_…",
    "failure_reason": "Tu tarjeta fue rechazada."
  }
}

payment.refund_required

{
  "event": "payment.refund_required",
  "timestamp": "2026-10-27T10:20:11.500Z",
  "policy": {
    "id": "b2a7…",
    "policy_number_internal": "POL-0046",
    "status": "rejected",
    "environment": "live",
    "payment_method_status": null,
    "refund_status": "pending"
  },
  "payment": {
    "id": "PAYMENT_ID",
    "provider": "stripe",
    "status": "paid",
    "amount": 50820,
    "currency": "ARS",
    "provider_ref": "pi_…"
  }
}
  • payment.succeeded y payment.failed van a la URL de pólizas, con un resumen de la póliza y el pago: id, provider, status, amount, currency, provider_ref y, si el proveedor lo informa, failure_reason.
  • Tras un payment.failed el asegurado puede reintentar: un pago aprobado después envía payment.succeeded y la póliza pasa a active. En productos condicionales el pago solo se habilita con las condiciones aprobadas.
  • payment.refund_required y payment.refunded (solo pólizas antiguas pagadas antes de sus condiciones) llevan el pago cobrado y policy.refund_status (pending o done). Quore nunca reembolsa por su cuenta.

quote.created

{
  "event": "quote.created",
  "timestamp": "2026-10-26T13:58:02.120Z",
  "quote": {
    "quoteId": "QUOTE_ID",
    "productId": "PRODUCT_ID",
    "variables": {
      "edad": 35,
      "plan": "full"
    },
    "status": "priced",
    "environment": "live",
    "coverages": [
      {
        "key": "basica",
        "totalPremium": 50820
      }
    ]
  }
}

quote.approved

{
  "event": "quote.approved",
  "timestamp": "2026-10-26T15:20:00.000Z",
  "quote": {
    "quoteId": "QUOTE_ID",
    "approvedBy": "USER_ID",
    "environment": "live"
  }
}

Firma

Headers de cada envío

POST /webhooks/quore HTTP/1.1
Content-Type: application/json
X-Quore-Event: policy.created
X-Quore-Delivery: 7d0e6c1a-…
X-Quore-Timestamp: 1793023391
X-Quore-Signature-V2: sha256=5f1c…
X-Quore-Signature: sha256=a93b…
  • X-Quore-Timestamp: momento del envío, en segundos Unix. Cambia en cada reintento.
  • X-Quore-Signature-V2: sha256= seguido del HMAC-SHA256 en hexadecimal de {timestamp}.{cuerpo} (el timestamp, un punto y el cuerpo crudo), con el secreto del webhook como clave.
  • Verifica la firma sobre el cuerpo crudo, exactamente como llegó (antes de parsear el JSON), y compárala en tiempo constante.
  • Rechaza con 401 los envíos sin firma, con firma inválida o con un timestamp a más de 5 minutos de tu reloj: así un envío capturado no se puede reutilizar.
  • X-Quore-Delivery identifica el envío y se mantiene entre reintentos: úsalo para descartar duplicados. X-Quore-Event repite el nombre del evento.

Verificar la firma

const crypto = require('crypto');
const express = require('express');

const TOLERANCE_SECONDS = 300;

app.post('/webhooks/quore', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-Quore-Timestamp') || '';
  const received = req.get('X-Quore-Signature-V2') || '';
  const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
  if (!timestamp || !Number.isFinite(age) || age > TOLERANCE_SECONDS) return res.sendStatus(401);

  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.QUORE_WEBHOOK_SECRET)
    .update(timestamp + '.')
    .update(req.body)
    .digest('hex');
  const valid = received.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!valid) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString('utf8'));
  res.sendStatus(200);
});

X-Quore-Signature (HMAC solo del cuerpo, sin timestamp) se sigue enviando por compatibilidad con integraciones existentes. No protege contra reenvíos: usa X-Quore-Signature-V2.

Entrega y reintentos

  • Cada evento se intenta hasta 3 veces: el primer intento al instante, el segundo 1 segundo después de que falla el primero y el tercero 3 segundos después de que falla el segundo. Cada intento espera hasta 10 segundos la respuesta.
  • Cualquier respuesta 2xx cuenta como entregado. Responde rápido y procesa en segundo plano.
  • Si los 3 intentos fallan, el evento queda registrado como fallido en el historial de la cuenta y no se vuelve a enviar: reconcilia con GET /api/policies o GET /api/quotes.
  • El orden de llegada no está garantizado y un evento puede llegar más de una vez: procésalos de forma idempotente (por X-Quore-Delivery, o por id de póliza y event).