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.
| Event | Fires when |
|---|---|
submission.created | A customer submits a configuration |
quote.created | An organization user (or the API) creates a quote |
quote.sent | A quote is sent to the customer |
quote.viewed | The customer opens the quote (first view) |
quote.accepted | The customer accepts |
quote.rejected | The customer rejects |
quote.expiring_soon | 3 days before a sent quote expires (daily check) |
quote.expired | A sent quote passes its validity date |
lead.created | A contact-form lead arrives on your public page |
pricing.calculated | A 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:
| Header | Value |
|---|---|
X-WinFactor-Event | The event type |
X-WinFactor-Delivery | Unique event ID — dedup on this; delivery is at-least-once |
X-WinFactor-Signature | HMAC 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.calculatedskips the queue cadence and is delivered immediately — it has to beat the configurator's timeout. - Self-serve debugging.
GET /api/v1/eventsis your delivery log (status, attempts, last error);POST /api/v1/events/{id}/redeliverre-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:
configurationis the canonical configuration — the same shape that is frozen on submission, so your pricing logic sees identical input live and at submission time.basePricing.lineItemsis echo-able: the array is valid as thelineItemsbody of the callbackPUTwith zero field surgery. Edit what you want and send it back. (itemKeyidentifies each base item:level:strategyId:sourceId.)callback.timeoutMsis 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.created—data.customer,data.template,data.configuration(width/height/unitAmount/componentCount), anddata.pricingwithsource: "external"+ your frozen line items when the live loop priced this submission, elsesource: "base".data.urls.submissionDetaillinks to the dashboard.quote.created/quote.sent— quote identity, totals, customer block,lineItems[];quote.sentadds the publicurls.viewQuote.quote.viewed/quote.accepted/quote.rejected— customer identity plus view/acceptance/rejection metadata (timestamps, IP, user agent where applicable).quote.expiring_soon/quote.expired—data.expirywithvalidUntiland (for expiring-soon)daysRemaining: 3.lead.created—data.contactwith 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.