quore/API documentation

Webhooks

Get notified in your system every time a product is quoted, issued or paid, signed so you can verify it comes from Quore.

Setup

  • Configured per product: one URL for policies and another for quotes, each with its signing secret.
  • Like the rest of the product, they apply once the version is published.
  • They are sent for every channel: API, widget, intermediary portal and client portal.

Events

EventURLWhen it is sent
policy.createdPoliciesWhen a policy is issued, through any channel.
policy.status_changedPoliciesWhen a pending policy changes status: pending_payment (conditions approved, with payment_url to pay), active or rejected, when its conditions are resolved, its payment is confirmed or it expires.
payment.succeededPoliciesWhen a policy's single online payment is confirmed (Stripe, Mercado Pago or Rebill).
payment.failedPoliciesWhen a collection attempt of a policy waiting for payment is declined: a single payment, the first period of a recurring product or the card validation of a usage one. Once per attempt (repeated provider events for the same attempt aren't resent) and includes failure_reason.
payment.refund_requiredPoliciesOnly older policies paid before their conditions were resolved: rejected after being paid (or paid after being rejected), they need a manual refund.
payment.refundedPoliciesWhen an admin marks that refund as done.
quote.createdQuotesWhen a quote is priced.
quote.pending_approvalQuotesWhen a quote that requires approval is priced.
quote.approvedQuotesWhen a pending quote is approved.
quote.rejectedQuotesWhen a pending quote is rejected (includes reason).

Payload

Every event is sent as a JSON POST: event, timestamp (ISO 8601) and the record (policy or quote; payment events add payment). policy.created carries the full policy:

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": "Producer",
        "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"
  }
}
FieldContent
dataIssuance data by group (insured, asset, coverage, general) and the masked payment method (payment: method, card_last4, card_brand, card_holder, provider).
net_premiumNet premium (before taxes).
total_premiumTotal premium (amount to pay: premium, taxes and financing surcharge).
tax_breakdownApplied taxes: a list of { id, name, amount }.
commissionsCommissions computed at quote time, or null: commission (tierId, tierName, type, rate, amount, affectsPricing) and ancestorCommissions (same shape plus intermediaryId and levelName, nearest ancestor first).
operator_idUser who issued it (intermediary or operator), or null when issued through the API or the public widget.
payment_method_statusRecurring or usage billing: pending until the insured saves a payment method, then active; null when not applicable. The policy activates with its first charge confirmed (recurring) or its card validated (usage).
environmentlive or test.

In policy.created the amounts (net_premium, total_premium, installment_amount) arrive as decimal strings (for example "42000"); convert them to numbers when processing.

The full card number is never sent, unless the product explicitly enables card forwarding in manual payment mode: then it arrives only in policy.created, separately, in payment_pan.card_number, and is never stored. The CVV is never requested, stored or sent.

policy.created with card forwarding enabled

{
  "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": "Inspection",
        "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": "Your card was declined."
  }
}

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 and payment.failed go to the policies URL, with a summary of the policy and the payment: id, provider, status, amount, currency, provider_ref and, when the provider reports it, failure_reason.
  • After a payment.failed the insured can retry: a later approved payment sends payment.succeeded and the policy becomes active. On conditional products the payment only opens once the conditions are approved.
  • payment.refund_required and payment.refunded (only older policies paid before their conditions) carry the collected payment and policy.refund_status (pending or done). Quore never refunds on its own.

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"
  }
}

Signature

Headers of every delivery

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: time of the delivery, in Unix seconds. It changes on every retry.
  • X-Quore-Signature-V2: sha256= followed by the hex HMAC-SHA256 of {timestamp}.{body} (the timestamp, a dot and the raw body), keyed with the webhook secret.
  • Verify the signature over the raw body, exactly as received (before parsing the JSON), and compare in constant time.
  • Reject with 401 deliveries without a signature, with an invalid one, or with a timestamp more than 5 minutes away from your clock: a captured delivery cannot be replayed.
  • X-Quore-Delivery identifies the delivery and stays the same across retries: use it to drop duplicates. X-Quore-Event repeats the event name.

Verify the signature

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 of the body only, no timestamp) is still sent for compatibility with existing integrations. It does not protect against replays: use X-Quore-Signature-V2.

Delivery and retries

  • Each event is attempted up to 3 times: the first attempt immediately, the second 1 second after the first fails and the third 3 seconds after the second fails. Each attempt waits up to 10 seconds for the response.
  • Any 2xx response counts as delivered. Answer fast and process in the background.
  • If the 3 attempts fail, the event is recorded as failed in the account history and is not sent again: reconcile with GET /api/policies or GET /api/quotes.
  • Arrival order is not guaranteed and an event may arrive more than once: process them idempotently (by X-Quore-Delivery, or by policy id and event).