WinFactor Docs

Events Catalog

Every webhook event WinFactor sends, with payload shapes and delivery semantics

WinFactor sends 10 event types. Nine cover the business lifecycle; pricing.calculated powers the live pricing loop.

EventFires when
submission.createdA customer submits a configuration
quote.createdAn organization user (or the API) creates a quote
quote.sentA quote is sent to the customer
quote.viewedThe customer opens the quote (first view)
quote.acceptedThe customer accepts
quote.rejectedThe customer rejects
quote.expiring_soon3 days before a sent quote expires (daily check)
quote.expiredA sent quote passes its validity date
lead.createdA contact-form lead arrives on your public page
pricing.calculatedA customer's live configuration needs your pricing

Common envelope

Every delivery is an HTTP POST to your webhook URL:

{
  "version": "1.0",
  "event": "quote.sent",
  "timestamp": "2026-06-11T14:03:22.000Z",
  "eventType": "quote.sent",
  "orgLocale": "en",
  "customerLocale": "de",
  "organization": {
    "id": "org_2abc…", "name": "Window Works Inc", "slug": "window-works",
    "email": "[email protected]", "phone": "+1 555 0100", "logoUrl": "https://…"
  },
  "data": { "…event-specific…": "see below" }
}

Headers on every delivery:

HeaderValue
X-WinFactor-EventThe event type
X-WinFactor-DeliveryUnique event ID — dedup on this; delivery is at-least-once
X-WinFactor-SignatureHMAC signature — verify it

Delivery semantics

  • At-least-once. Failed deliveries (non-2xx, timeout) retry with backoff (~1, 5, 15 minutes; 3 attempts). Always dedup on X-WinFactor-Delivery.
  • Fast path for live events. pricing.calculated skips the queue cadence and is delivered immediately — it has to beat the configurator's timeout.
  • Self-serve debugging. GET /api/v1/events is your delivery log (status, attempts, last error); POST /api/v1/events/{id}/redeliver re-sends any delivered/failed event — your recovery tool after an outage. Events API →

pricing.calculated

The live pricing hook. Fired with context:

  • "live" — the customer changed something in the configurator (debounced; one in-flight request per configuration)
  • "submission" — the customer submitted and no adjustment matched the final configuration — your one authoritative chance before the quote stage
  • "autofill" — an organization user clicked auto-fill on the quote form; always fired so your pricing is fresh
{
  "event": "pricing.calculated",
  "data": {
    "context": "live",
    "session": { "id": "k97…", "origin": "org_customize" },
    "template": { "id": "k12…", "name": "2-panel Lift-Slide Door", "slug": "2-panel-lift-slide" },
    "configuration": {
      "width": 2400, "height": 1800,
      "components": [
        { "componentId": "k34…", "width": 1185, "height": 1740,
          "selectedOptions": [{ "optionId": "k88…" }] }
      ],
      "insideColorId": "k55…", "outsideColorId": "k56…"
    },
    "configHash": "sha256:7be1…",
    "submission": null,
    "basePricing": {
      "currency": "EUR",
      "subtotal": 1240.5,
      "lineItems": [
        { "itemKey": "component:k71…:k34…", "description": "Lift-slide door panel",
          "quantity": 2, "unitPrice": 480.0, "total": 960.0,
          "itemType": "door", "sortOffset": 10 },
        { "itemKey": "option:k72…:k88…", "description": "HR++ glass",
          "quantity": 2, "unitPrice": 140.25, "total": 280.5, "sortOffset": 20 }
      ]
    },
    "callback": {
      "url": "https://app.winfactor.app/api/v1/configuration-sessions/3fA9…/adjustments",
      "method": "PUT",
      "configHash": "sha256:7be1…",
      "timeoutMs": 10000
    }
  }
}

Key properties:

  • configuration is the canonical configuration — the same shape that is frozen on submission, so your pricing logic sees identical input live and at submission time.
  • basePricing.lineItems is echo-able: the array is valid as the lineItems body of the callback PUT with zero field surgery. Edit what you want and send it back. (itemKey identifies each base item: level:strategyId:sourceId.)
  • callback.timeoutMs is how long the customer's configurator shows "confirming final price" before falling back to base pricing. Answers after the timeout still apply (the screen self-heals), but aim to respond inside it.
  • Dimensions are integer millimetres; map them yourself if you price imperial.

Full write-back contract: Configuration sessions.

Business events (summary)

The nine business events are unchanged from previous versions; highlights:

  • submission.createddata.customer, data.template, data.configuration (width/height/unitAmount/componentCount), and data.pricing with source: "external" + your frozen line items when the live loop priced this submission, else source: "base". data.urls.submissionDetail links to the dashboard.
  • quote.created / quote.sent — quote identity, totals, customer block, lineItems[]; quote.sent adds the public urls.viewQuote.
  • quote.viewed / quote.accepted / quote.rejected — customer identity plus view/acceptance/rejection metadata (timestamps, IP, user agent where applicable).
  • quote.expiring_soon / quote.expireddata.expiry with validUntil and (for expiring-soon) daysRemaining: 3.
  • lead.createddata.contact with name/email/phone/message.

For any event, fetch the full current resource by ID through the REST API — everything an event mentions is fetchable, including a human-readable resolvedConfiguration on submissions.

On this page