Webhooks
The Platform GraphQL API supports webhooks so your backend receives a real-time HTTP notification when a relevant event occurs for brokers in your tree.
Registering a webhook
Use the registerWebhook mutation to subscribe a URL to one or more events. Registering the same URL a second time merges the event set — you will not create duplicates.
mutation RegisterWebhook($input: RegisterWebhookInput!) {
registerWebhook(input: $input) {
id
url
events
createdAt
}
}
{
"input": {
"url": "https://your-platform.example/webhooks/lla",
"events": ["OFFER_CREATED", "OFFER_UPDATED", "REWORK_CREATED", "REWORK_UPDATED", "POLICY_ISSUED"],
"verificationSecret": "your-hmac-secret"
}
}
Listing registered webhooks
query Webhooks {
webhooks {
id
url
events
createdAt
}
}
Removing a webhook
mutation UnregisterWebhook($input: UnregisterWebhookInput!) {
unregisterWebhook(input: $input) {
__typename
... on Success {
status
}
... on WebhookNotFoundError {
message
}
}
}
{
"input": {
"url": "https://your-platform.example/webhooks/lla"
}
}
Events
OFFER_CREATED
Fires when an offer is created for any broker in your tree — including offers created directly in the Liechtenstein Life insurance system, not only offers submitted through this API.
Payload
{
"contractID": "PR-123456",
"offerId": "abc123",
"brokerLifewareId": "196856",
"event": "offer_created"
}
| Field | Type | Description |
|---|---|---|
contractID | string | The policy / offer reference in the insurance system. Use this as the contractID in offerStatus queries. |
offerId | string | Internal offer identifier. |
brokerLifewareId | string | Lifeware ID of the broker who owns the offer. |
event | "offer_created" | Literal event name. |
The notification fires when the offer is first seen by the sync pipeline — not necessarily at the instant of creation in the insurance system. Lifeware-direct offers appear when the offer-sync scheduler picks them up.
OFFER_UPDATED
Fires when an already-known offer changes a meaningful field — its offer status, signature status, or application / cancellation date. It does not fire on routine re-syncs that change nothing.
Payload
{
"contractID": "PR-123456",
"offerId": "abc123",
"brokerLifewareId": "196856",
"changedFields": ["offerStatus", "signatureStatus"],
"event": "offer_updated"
}
| Field | Type | Description |
|---|---|---|
contractID | string | Policy / offer reference. |
offerId | string | Internal offer identifier. |
brokerLifewareId | string | Lifeware ID of the broker who owns the offer. |
changedFields | string[] | Which fields moved since the previous sync (e.g. offerStatus, signatureStatus). |
event | "offer_updated" | Literal event name. |
REWORK_CREATED
Fires when a rework — a request from the insurer for missing information or a correction before the offer can proceed — is opened on an offer/policy in your tree.
Payload
{
"taskId": "3f1c0b7e-1a2b-4c3d-8e9f-000000000000",
"contractID": "PR-123456",
"reason": "Passport copy for the insured person is missing",
"status": "WAITING",
"openedOn": "2026-06-30T00:00:00.000Z",
"closedOn": null,
"event": "rework_created"
}
| Field | Type | Description |
|---|---|---|
taskId | string | Unique rework identifier. |
contractID | string | Policy reference the rework belongs to. |
reason | string | Why the rework was raised. |
status | "WAITING" | "FROZEN" | "FINISHED" | WAITING = awaiting your response; FROZEN = you replied, internal verification pending; FINISHED = resolved. |
openedOn | string | Date the rework opened (date-only, serialized as a midnight-UTC ISO timestamp). |
closedOn | string | null | Date the rework closed, or null while still open. |
event | "rework_created" | Literal event name. |
REWORK_UPDATED
Same payload shape as REWORK_CREATED, with event: "rework_updated". Fires when an existing rework changes — typically a status transition (WAITING → FROZEN → FINISHED). The closing transition arrives as a final REWORK_UPDATED with status: "FINISHED" and a non-null closedOn.
After it closes, the rework no longer appears in the openReworks query or in offerStatus.openReworks — both of which list only WAITING reworks. The webhook is therefore the only way to observe FROZEN and FINISHED transitions.
Rework events fire when the rework-sync pipeline observes the change. If the related offer has not yet been synced to your tree, the notification is sent on a later sync cycle once the offer is known.
POLICY_ISSUED
Fires once, when an offer/application in your tree is issued as a policy in the insurance system. Like the other events, this is detected by the sync pipeline, so it covers policies issued outside this API too.
Payload
{
"contractID": "PR-123456",
"offerId": "abc123",
"brokerLifewareId": "196856",
"event": "policy_issued"
}
| Field | Type | Description |
|---|---|---|
contractID | string | The issued policy reference. Query it via offer / offerStatus. |
offerId | string | Internal offer identifier the policy was issued from. |
brokerLifewareId | string | Lifeware ID of the broker who owns the policy. |
event | "policy_issued" | Literal event name. |
After issuance, offerStatus(contractID).status reports ISSUED. The event fires only on the first transition to issued, not on subsequent re-syncs.
Verifying webhook signatures
Every delivery includes an x-hmac header: a hex-encoded HMAC-SHA256 of the raw request body, keyed with your verification secret.
Pass your verification secret in the verificationSecret field when registering a webhook. Keep it secret — it is the only mechanism that proves a delivery came from us.
Example — Node.js / TypeScript
import { createHmac, timingSafeEqual } from "crypto"
function isValidWebhook(
body: string,
signature: string,
secret: string,
): boolean {
const expected = createHmac("sha256", secret)
.update(body)
.digest("hex")
return timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
)
}
Always compare with a constant-time function (timingSafeEqual) to prevent timing attacks.
Delivery details
| Property | Value |
|---|---|
| Method | POST |
| Content-Type | application/json |
x-webhook-id | Unique delivery identifier |
x-hmac | HMAC-SHA256 signature (hex) |
| User-Agent | prosperity-solutions/1.0.0 (+https://api.prosperity.app/partner/v1/webhook) |
| Retry policy | 5 attempts with exponential back-off (initial delay 5 s) |
Return 2xx to acknowledge receipt. Any other status code triggers a retry. Failed deliveries are retained; contact support if you need to replay a specific delivery.
Delivery guarantees
Webhooks are a best-effort, at-least-once notification: a delivery may be retried (so the same event can arrive more than once) and, in rare cases, may be missed entirely during an infrastructure incident. Design your integration accordingly:
- Reconcile, don't rely solely on webhooks. Treat the GraphQL queries as the source of truth — on receipt, and periodically as a safety net, fetch the authoritative state with
offer(contractID)/offerStatus(contractID)(andopenReworksfor reworks). A missed webhook then self-heals on the next reconciliation. - Be idempotent. The same event may be delivered more than once. Deduplicate on the natural id for the event —
offerIdfor offers,taskIdfor reworks,contractIDforPOLICY_ISSUED— and make your handler safe to run twice. - Don't assume ordering. Events are not guaranteed to arrive in the order they occurred. Use the queried state (e.g.
offerStatus.status, the reworkstatus) as authoritative rather than inferring it from the sequence of webhooks.
Deduplicate on the payload's natural id (above), not on x-webhook-id — that header identifies a delivery, and the same underlying event can be delivered under different ids.
Access requirement
Your API key must have the WEB_HOOK_MANAGEMENT permission to register, list, or remove webhooks. Contact your integration support contact if this permission is not active on your key.