Skip to main content

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", "POLICY_PICKED_UP"],
"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",
"persons": [
{ "customerID": "af2490b7-a779-11f1-93fd-0050568cdbcc", "role": "POLICY_HOLDER" },
{ "customerID": "af2490b7-a779-11f1-93fd-0050568cdbcc", "role": "INSURED_PERSON" }
],
"event": "offer_created"
}
FieldTypeDescription
contractIDstringThe policy / offer reference in the insurance system. Use this as the contractID in offerStatus queries.
offerIdstringInternal offer identifier.
brokerLifewareIdstringLifeware ID of the broker who owns the offer.
personsobject[]Everyone on the offer, as customerID and role pairs. A person holding several roles appears once per role. Pass a customerID to the customer query, or to createOffer as existingCustomer.byID.ID. The list is empty when the insurance system has not yet placed anyone on the offer; no later event is guaranteed to fill it in, so treat an empty list as a prompt to query the offer rather than as something to wait on.
event"offer_created"Literal event name.
Timing

The notification fires when the sync pipeline first observes an offer with a contractID for its broker. If the offer was already synced without a contractID, the notification fires on the sync that first receives that reference. A status change on that same sync is included in the initial offer state and does not produce a separate OFFER_UPDATED event. Later tracked changes produce OFFER_UPDATED events. 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",
"persons": [
{ "customerID": "af2490b7-a779-11f1-93fd-0050568cdbcc", "role": "POLICY_HOLDER" },
{ "customerID": "af2490b7-a779-11f1-93fd-0050568cdbcc", "role": "INSURED_PERSON" }
],
"changedFields": ["offerStatus", "signatureStatus"],
"event": "offer_updated"
}
FieldTypeDescription
contractIDstringPolicy / offer reference.
offerIdstringInternal offer identifier.
brokerLifewareIdstringLifeware ID of the broker who owns the offer.
personsobject[]Everyone on the offer, as customerID and role pairs. A person holding several roles appears once per role. Pass a customerID to the customer query, or to createOffer as existingCustomer.byID.ID. The list is empty when the insurance system has not yet placed anyone on the offer; no later event is guaranteed to fill it in, so treat an empty list as a prompt to query the offer rather than as something to wait on.
changedFieldsstring[]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"
}
FieldTypeDescription
taskIdstringUnique rework identifier.
contractIDstringPolicy reference the rework belongs to.
reasonstringWhy the rework was raised.
status"WAITING" | "FROZEN" | "FINISHED"WAITING = awaiting your response; FROZEN = you replied, internal verification pending; FINISHED = resolved.
openedOnstringDate the rework opened (date-only, serialized as a midnight-UTC ISO timestamp).
closedOnstring | nullDate 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.

Timing

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",
"persons": [
{ "customerID": "af2490b7-a779-11f1-93fd-0050568cdbcc", "role": "POLICY_HOLDER" },
{ "customerID": "af2490b7-a779-11f1-93fd-0050568cdbcc", "role": "INSURED_PERSON" }
],
"event": "policy_issued"
}
FieldTypeDescription
contractIDstringThe issued policy reference. Query it via offer / offerStatus.
offerIdstringInternal offer identifier the policy was issued from.
brokerLifewareIdstringLifeware ID of the broker who owns the policy.
personsobject[]Everyone on the offer, as customerID and role pairs. A person holding several roles appears once per role. Pass a customerID to the customer query, or to createOffer as existingCustomer.byID.ID. The list is empty when the insurance system has not yet placed anyone on the offer; no later event is guaranteed to fill it in, so treat an empty list as a prompt to query the offer rather than as something to wait on.
event"policy_issued"Literal event name.
note

After issuance, offerStatus(contractID).status reports ISSUED. The event fires only on the first transition to issued, not on subsequent re-syncs.

POLICY_PICKED_UP​

Fires when digital receipt of a policy is confirmed in the customer app, or when the policy sync first records a pickup reported by the insurance system. It carries the recorded pickup date, which can precede the notification.

Register the event on your existing URL to merge it into that URL's subscriptions. Existing subscriptions remain active. Registering and receiving this event requires POLICY_READ in addition to WEB_HOOK_MANAGEMENT.

Payload

{
"contractID": "PR-123456",
"pickedUpAt": "2026-07-29T14:30:00.000Z",
"event": "policy_picked_up"
}
FieldTypeDescription
contractIDstringThe policy number. Matches Policy.id in the policies query.
pickedUpAtstringThe recorded digital pickup date and time, serialized as an ISO 8601 timestamp.
event"policy_picked_up"Literal event name.

App registration, inbox confirmation, a document download, and first-premium payment alone do not trigger this event. Pickup notifications do not change postal delivery settings.

Delivery checks the policy's current broker ownership, pickup date, subscription, and integration permission. A policy transfer, removed confirmation, or revoked permission can therefore suppress a queued notification.

This subscription does not replay historical pickups. Read Policy.pickedUpAt through the policies query for existing policies and periodic reconciliation. A repeated confirmation or delivery retry can produce the same notification more than once. Deduplicate by (contractID, pickedUpAt).

PRE_REGISTRATION_CREATED​

Fires when a Vermittler in your tree adds an Altersvorsorgedepot Vormerkung: either a pre-registration written directly into a customer's app account, or an invitation sent to someone who has to register first. It fires for Vormerkungen added in Partner Life and through createPreRegistration alike. It does not fire when a customer completes a pre-registration in the app on their own, when an invited person claims an invitation, or when a Vermittler edits an existing Vormerkung.

The payload identifies the entry only. Read its details, including the customer's email, with the preRegistrations query.

Payload

{
"id": "6f1b8c2e-4d3a-4f5b-9c7d-2e8a1b0c9d4f",
"kind": "INVITATION",
"responsiblePartnerId": "250430",
"event": "pre_registration_created"
}
FieldTypeDescription
idstringThe entry's id in the preRegistrations query. For an entry your integration created, it is the preRegistrationId or invitationId that createPreRegistration returned.
kind"PRE_REGISTRATION" | "INVITATION"PRE_REGISTRATION is held in a customer's app account (__typename: PreRegistration in the query). INVITATION is waiting for the invited person to register (__typename: PreRegistrationInvitation).
responsiblePartnerIdstringLifeware ID of the broker in your tree the entry is attributed to. For an entry a broker's colleague prepared, this is the broker.
event"pre_registration_created"Literal event name.

Registering this event requires the PRE_REGISTRATION_READ permission in addition to WEB_HOOK_MANAGEMENT.

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​

PropertyValue
MethodPOST
Content-Typeapplication/json
x-webhook-idUnique delivery identifier
x-hmacHMAC-SHA256 signature (hex)
User-Agentprosperity-solutions/1.0.0 (+https://api.prosperity.app/partner/v1/webhook)
Retry policy5 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) (policies for pickup dates, openReworks for reworks, preRegistrations for Vormerkungen). 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 — offerId for offers, taskId for reworks, contractID for POLICY_ISSUED, (contractID, pickedUpAt) for POLICY_PICKED_UP, id for PRE_REGISTRATION_CREATED — 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 rework status) as authoritative rather than inferring it from the sequence of webhooks.
note

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. Registering PRE_REGISTRATION_CREATED also requires PRE_REGISTRATION_READ. Registering POLICY_PICKED_UP also requires POLICY_READ; without the required permission, the whole registration is refused. Contact your integration support contact if a permission is not active on your key.