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
| Evento | URL | Cuándo se envía |
|---|---|---|
policy.created | Pólizas | Al emitirse una póliza, por cualquier canal. |
policy.status_changed | Pólizas | Cuando 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.succeeded | Pólizas | Al confirmarse el pago único online de una póliza (Stripe, Mercado Pago o Rebill). |
payment.failed | Pólizas | Al 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_required | Pólizas | Solo pólizas antiguas pagadas antes de resolver sus condiciones: al rechazarse ya pagadas (o pagarse ya rechazadas) requieren un reembolso manual. |
payment.refunded | Pólizas | Al marcar un administrador ese reembolso como hecho. |
quote.created | Cotizaciones | Al calcularse una cotización. |
quote.pending_approval | Cotizaciones | Al calcularse una cotización que requiere aprobación. |
quote.approved | Cotizaciones | Al aprobarse una cotización pendiente. |
quote.rejected | Cotizaciones | Al 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"
}
}| Campo | Contenido |
|---|---|
data | Datos 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_premium | Prima (sin impuestos). |
total_premium | Premio (total a pagar: prima, impuestos y recargo financiero). |
tax_breakdown | Impuestos aplicados: lista de { id, name, amount }. |
commissions | Comisiones 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_id | Usuario que emitió (intermediario u operador), o null si se emitió por API o widget público. |
payment_method_status | Cobro 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). |
environment | live 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.succeededypayment.failedvan a la URL de pólizas, con un resumen de la póliza y el pago:id,provider,status,amount,currency,provider_refy, si el proveedor lo informa,failure_reason.- Tras un
payment.failedel asegurado puede reintentar: un pago aprobado después envíapayment.succeededy la póliza pasa aactive. En productos condicionales el pago solo se habilita con las condiciones aprobadas. payment.refund_requiredypayment.refunded(solo pólizas antiguas pagadas antes de sus condiciones) llevan el pago cobrado ypolicy.refund_status(pendingodone). 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
401los 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-Deliveryidentifica el envío y se mantiene entre reintentos: úsalo para descartar duplicados.X-Quore-Eventrepite 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/policiesoGET /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 poridde póliza yevent).