Policy Events

Every change to a policy is recorded as an immutable policy event. Events are appended in order, are never edited, and together form the full history of the policy. They are the record you should reconcile against, rather than the current policy object alone.

You receive policy events in two ways:

  • Webhooks push an event to your endpoint as it happens. See Webhooks.
  • The policy events endpoint returns the full history on demand. See List policy events.

Both surfaces are produced by the same output mapping, so an event has the same type and field names whether it arrived by webhook or was retrieved later.

This guide explains the shape of an event, which events each type of policy change produces and in what order, how to interpret events that can carry different kinds of change, and how to read the premium fields on an event.

The event envelope

Every policy event carries the same common fields, followed by fields that are specific to its type.

FieldTypeDescription
policy_idstringThe policy the event belongs to.
typestringThe event type, for example policy_issued. See Full event type reference.
event_versionnumberThe event's sequence number within the policy. Increases by one per event. Use it to order events on the same policy.
created_atstringISO 8601 timestamp at which the event was recorded.
requested_byobjectWho caused the event: { "type": "...", "id": "..." }. See the table below.
metadataobjectRequest tracing metadata. Includes requested_by and requested_at.
field_changesarrayOptional. A list of { "path", "before", "after" } entries describing exactly which policy fields the event changed. See below.

requested_by.type

ValueMeaning
api_keyAn API call authenticated with an API key. id is the API key ID.
userA user acting in the dashboard. id is the user ID.
systemA scheduled or automated platform process, for example the lapse run.
product_moduleAn action returned by product module code.
embed_jwtA call made from an embedded customer flow.
collection_moduleA collection module.
collection_module_tokenA call authenticated with a collection module token.
📘

About field_changes

field_changes is a precise diff of the policy before and after the event. Each entry has a dot-separated path in the policy's internal camelCase field naming (for example monthlyPremium or module.vehicleValue), the value before, and the value after. A field that was added has no before; a field that was removed has no after.

  • It is only present when the field changes feature is enabled for your organisation. Ask your Root contact if you do not see it.
  • It is omitted when the event changed nothing.
  • It is capped at 100 entries.

Fields with no value are omitted from event payloads entirely rather than sent as null. The absence of a key means "not set or not changed", not "changed to null".

Example of a minimal event:

{
  "policy_id": "7a4f4a7e-2b0e-4a4b-9a1e-6b3c7e2a9d10",
  "type": "policy_activated",
  "event_version": 4,
  "created_at": "2026-03-02T08:15:41.120Z",
  "requested_by": {
    "type": "system"
  },
  "metadata": {
    "requested_by": { "type": "system" },
    "requested_at": "2026-03-02T08:15:41.099Z"
  }
}

Where events are delivered

Webhooks

A policy webhook delivery has this body:

FieldDescription
webhook_idThe ID of the webhook configuration that produced the delivery.
environmentsandbox or production.
verification_tokenYour configured verification token, if any.
eventThe policy event, in the envelope described above.
policyThe full policy object after the event was applied. Includes restricted_data.

The name of the subscription that triggered the delivery is not in the body. Always discriminate on event.type. In one case a subscription delivers an event of a different type; see Notes for reconciliation.

Retrieving the event history

List policy events returns every event on a policy, newest first. The endpoint is not paginated; use created_at_from, created_at_to, version_from and version_to to narrow the range on policies with long histories. Use it to backfill, to recover from a missed webhook, or to look up the state of a policy immediately before a given event.

Events by policy change

Each section lists the events in the order they are recorded, and then the events that follow asynchronously. Events marked async are queued after the request completes and can arrive out of order relative to each other.

Policy issue

  1. policy_issued — the full initial state of the policy.
  2. text_note_created — one per note carried over from the application, if any.
  3. policy_activated — if the product is configured to activate on issue. Otherwise it is recorded later, when the configured activation event occurs (for example the first successful payment).
  4. payment_method_assigned — if a payment method was supplied on the application.
  5. async policy_terms_updated, policy_schedule_updated, policy_welcome_letter_updated and policy_certificate_updated as each document is generated.

The policy_issued event carries: policy_number, package_name, sum_assured, monthly_premium, base_premium, billing_frequency, billing_amount, billing_month, billing_day, beneficiaries, start_date, end_date, module, app_data, restricted_data, application_id, policyholder_id, scheme_type, covered_items, currency, product_module_id, product_module_definition_id, payment_method_id, charges, status and field_changes.


Alteration packages

An alteration is any post-issue change made through an alteration hook: a change of cover, a change of premium, a change of policy details, or all three. It happens in two steps.

Create. Calling the create alteration package endpoint runs the hook and stores a package with status: "pending". No policy event is recorded. The alteration_package_created webhook is sent with the package object.

Apply. Applying the package records exactly one policy event, policy_alteration_package_applied, and sets the package status to "applied". No policy_updated event is recorded for an alteration.

  1. policy_alteration_package_applied
  2. async policy_schedule_updated, and policy_certificate_updated where the product issues certificates.
FieldTypeDescription
alteration_package_idstringThe package that was applied. Retrieve it with the alteration package endpoints for input_data and change_description.
alteration_keystringThe key of the alteration hook that produced the package, as defined in your product module.
alteration_namestringThe display name of the alteration hook.
package_namestringThe policy's package name after the alteration.
sum_assuredintegerThe sum assured after the alteration, in cents.
monthly_premiumintegerThe monthly premium after the alteration, in cents. See Premium fields on events.
base_premiumintegerThe base premium after the alteration, in cents.
billing_amountintegerThe amount that will be billed at the next billing run, in cents. Set equal to monthly_premium on apply.
billing_frequencystringPresent when the product allows billing frequency changes.
billing_monthintegerPresent alongside billing_frequency.
start_date, end_datestringPresent when the alteration changed them.
moduleobjectThe product-specific policy data after the alteration.
restricted_dataobjectRestricted policy data after the alteration, if the hook returned it.
chargesarrayThe policy's charges after the alteration, if the hook returned them. See Charges and commissions.
field_changesarrayThe diff between the policy before and after the alteration.

Cancellation and reactivation

  1. policy_cancelled. No document event follows a cancellation; notifications are sent but nothing further is recorded on the policy.

If the product's beforePolicyCancelled lifecycle hook prevents the cancellation, the cancel request fails with an error and no event is recorded. policy_cancel_failed is recorded only when a scheduled cancellation (one created with a future action date through the bulk cancel endpoint) cannot be actioned on that date, for example because the policy was cancelled in the meantime. It carries failure_reason.

FieldTypeDescription
reasonstringFree-text reason supplied on cancellation.
cancellation_requestorstringinsurer or client.
cancellation_typestringThe selected reason category. Insurer values: Cooling off period, Dishonest client, Altered risk profile, Policy claimed. Client values: Too expensive, Alternate product, Unhappy with service, Unhappy with benefits, Financial constraints, Retrenched, Unemployed, Leaving the country, and further product-configured options.
failure_reasonstringOn policy_cancel_failed only. Why the scheduled cancellation could not be actioned.

Reactivating a cancelled or lapsed policy records policy_reactivated with status (the status the policy returned to), reactivation_option (the option object from your product module, including its type), module if the reactivation hook changed it, and field_changes. A policy_schedule_updated event follows asynchronously.



Lapse, expiry and not taken up

These are status changes made by the platform's scheduled processes, so requested_by.type is usually system. Each carries only the common envelope unless stated.

EventWhen
policy_lapsedThe policy lapsed under your product's lapse rules.
policy_lapse_failedA scheduled lapse could not be actioned on its action date. Carries failure_reason.
policy_expiredThe policy reached its end_date.
policy_not_taken_upThe policy was marked not taken up, automatically or manually.
policy_lapse_and_ntu_preventedLapse and not-taken-up processing was suspended for the policy for a period.
policy_lapse_and_ntu_resumedThe suspension ended.

Other policy updates

EventWhat changed
policy_updatedOne or more top-level policy fields changed outside of an alteration. See Interpreting policy_updated.
policy_premium_updatedThe premium was recalculated from the policy's covered items. Carries monthly_premium.
policy_billing_updatedThe billing amount was set directly. Carries billing_amount.
policy_alteredThe beneficiaries or the sum assured were changed through their dedicated endpoints. Carries alteration, either beneficiaries_updated or sum_assured_updated, plus the new values.
policy_requotedThe policy was re-rated from a new quote.
policy_bumpedThe policy was moved to a newer product module definition.

Payment methods and billing instruments

EventWhat changed
payment_method_assignedA payment method was attached. Carries payment_method_id.
payment_method_removedThe payment method was detached. Carries payment_method_id.
policy_debicheck_mandate_updatedA DebiCheck mandate was linked or unlinked. Carries debi_check_mandate_id.

Full event type reference

GroupEvent types
Lifecycle and statuspolicy_issued, policy_imported, policy_activated, policy_cancelled, policy_cancel_failed, policy_lapsed, policy_lapse_failed, policy_expired, policy_not_taken_up, policy_reactivated, policy_lapse_and_ntu_prevented, policy_lapse_and_ntu_resumed, policy_flushed
Changespolicy_updated, policy_alteration_package_applied, policy_requoted, policy_premium_updated, policy_sum_assured_updated, policy_billing_updated, policy_beneficiaries_updated, beneficiary_updated, policy_members_updated, policy_covered_people_updated, policy_bumped, policy_refund_requested
Payment methodspayment_method_added_event, payment_method_assigned, payment_method_removed, policy_debicheck_mandate_updated, policy_receipt_created
Documentspolicy_terms_updated, policy_schedule_updated, policy_welcome_letter_updated, policy_certificate_updated, policy_anniversary
Notes and attachmentstext_note_created, note_pinned, note_unpinned, note_pins_reordered, note_content_overridden, attachment_created, attachment_archived
Linksclaim_linked, complaint_linked
Invoicesinvoice_created, invoice_sent, invoice_paid, invoice_voided, invoice_refunded, invoice_uncollectible

* Delivered with type: "policy_altered" and an alteration field. See Notes for reconciliation.

Interpreting policy_updated

policy_updated records a change to one or more top-level policy fields that did not go through an alteration hook. The event carries only the fields that were set, all optional, with no wrapper object:

app_data, billing_day, billing_month, billing_amount, package_name, sum_assured, base_premium, monthly_premium, start_date, end_date, policy_number, policyholder_id, module, restricted_data, lapse_and_ntu_prevention_start_date, lapse_and_ntu_prevention_end_date, field_changes.

There is no reason or source field. To determine what caused a policy_updated, combine the fields present with requested_by.type:

Fields presentrequested_by.typeCause
Only policyholder_idanyThe policy was reassigned to a different policyholder.
Only billing_daysame as the adjacent payment_method_assignedA payment method type with a fixed billing day was assigned, and the policy's billing day was aligned to it. Recorded immediately after payment_method_assigned, by the same requester.
Anyproduct_moduleAn update_policy action returned by a lifecycle hook or scheduled function.
app_data, billing_day, billing_month, start_date, end_dateapi_key, user, embed_jwtThe update policy endpoint or the dashboard.

When field_changes is enabled, prefer it over the presence of fields: it lists only what actually changed, whereas a field can be present on the event with the same value it already had.

{
  "policy_id": "7a4f4a7e-2b0e-4a4b-9a1e-6b3c7e2a9d10",
  "type": "policy_updated",
  "event_version": 9,
  "created_at": "2026-04-14T10:02:17.503Z",
  "requested_by": {
    "type": "product_module"
  },
  "billing_day": 25,
  "module": {
    "type": "funeral_cover",
    "cover_amount": 3000000,
    "commission_percentage": 12.5
  },
  "field_changes": [
    { "path": "billingDay", "before": 1, "after": 25 },
    { "path": "module.commissionPercentage", "before": 10, "after": 12.5 }
  ]
}
🚧

Charges are not changed by policy_updated

The policy's charges breakdown is set by the policy issue hook and by alteration hooks. A change to charges therefore appears on policy_alteration_package_applied (in its charges field and in field_changes under charges.*), and on the policy object attached to webhook deliveries. Product-specific values your module stores under module or restricted_data can change through either policy_updated or policy_alteration_package_applied; field_changes tells you which values moved.

Detecting a premium change on an alteration

Every policy_alteration_package_applied event carries monthly_premium, whether or not the premium changed. There is no flag that says "this alteration affected the premium". Determine it from the data:

  1. If field_changes is present, look for an entry with path equal to monthlyPremium. Its absence means the premium did not change.
  2. Otherwise, compare the event's monthly_premium to the policy's monthly_premium immediately before the event. That is the monthly_premium on the most recent earlier event that carries one (policy_issued, policy_alteration_package_applied, policy_updated, policy_premium_updated, policy_members_updated or policy_requoted).

A premium-neutral alteration, for example a corrected vehicle registration:

{
  "type": "policy_alteration_package_applied",
  "event_version": 6,
  "alteration_package_id": "b3d2f0a1-5c7e-4d0a-8f4b-2e1c9a7d6f30",
  "alteration_key": "update_vehicle_details",
  "alteration_name": "Update vehicle details",
  "package_name": "Comprehensive",
  "sum_assured": 25000000,
  "monthly_premium": 84900,
  "base_premium": 79900,
  "billing_amount": 84900,
  "module": { "type": "motor", "registration": "CA 123 456" },
  "field_changes": [
    { "path": "module.registration", "before": "CA 123 465", "after": "CA 123 456" }
  ]
}

A premium-affecting alteration, for example an increase in cover:

{
  "type": "policy_alteration_package_applied",
  "event_version": 7,
  "alteration_package_id": "0c8e6b12-9d3a-4e5f-b7a1-4f2d8c1e9a55",
  "alteration_key": "change_cover",
  "alteration_name": "Change cover amount",
  "package_name": "Comprehensive",
  "sum_assured": 30000000,
  "monthly_premium": 97200,
  "base_premium": 91500,
  "billing_amount": 97200,
  "module": { "type": "motor", "registration": "CA 123 456" },
  "field_changes": [
    { "path": "sumAssured", "before": 25000000, "after": 30000000 },
    { "path": "monthlyPremium", "before": 84900, "after": 97200 },
    { "path": "basePremium", "before": 79900, "after": 91500 },
    { "path": "billingAmount", "before": 84900, "after": 97200 }
  ]
}

The difference between the two premiums (here 12 300 cents per month) is not stored on the event. For policies billed on a payment schedule the platform records the signed monthly difference internally so that the next scheduled amount tracks the new premium; that record is not exposed as a policy event or webhook.

Premium fields on events

📘

monthly_premium is always the new recurring amount

On every event that carries it, monthly_premium is the full monthly premium that applies from the event onward, in cents. It is not annualised, it is not pro-rated to the remainder of the policy year, and it is not the difference from the previous premium.

FieldMeaning
monthly_premiumThe total monthly premium in cents that will be billed to the policyholder going forward.
base_premiumThe minimum allowed monthly premium in cents, including risk pricing and platform fees, before any product-specific loadings or discounts.
billing_amountThe amount in cents that will be collected at the next billing run. It can be set independently of monthly_premium, for example for a discount. Applying an alteration resets it to monthly_premium.
billing_frequencymonthly, yearly, weekly, daily or once_off. This is the only signal of how often the policy is billed; there is no annual premium field. For a yearly policy, monthly_premium remains the per-month figure and billing_amount is what is collected.
sum_assuredThe maximum value insured, in cents.

Applying an alteration does not create a pro-rata charge or refund for the period already billed. If your product needs to charge or refund for a mid-cycle change, raise it from the product module using a change policy balance action, which then appears in the policy's payment data rather than as a policy event.

Linking events to the policy and to each other

KeyLinks to
policy_idThe policy. Present on every event.
event_versionOrdering within one policy. Consecutive integers; a gap means you have missed an event.
created_atOrdering across policies.
alteration_package_id on policy_alteration_package_appliedThe alteration package. Retrieve it for input_data, change_description and the alteration_hook. Packages do not record an applied time; use the event's created_at.
application_id, policyholder_id, payment_method_id on policy_issuedThe application the policy was issued from, the policyholder, and the initial payment method.
payment_method_id on payment_method_assigned / payment_method_removedThe payment method.
requested_by.idThe user, API key or other principal that caused the event.
policy object on a webhook deliveryThe full policy state immediately after the event. Its monthly_premium, sum_assured, charges and status reflect the event.

Notes for reconciliation

🚧

Behaviours to account for

  • A policy_updated webhook subscription also delivers payment method removals. When a payment method is removed, the platform sends the payment_method_removed event to both the policy_updated and payment_method_removed subscriptions. Both deliveries carry event.type: "payment_method_removed". If you subscribe to both, de-duplicate on policy_id and event_version.
  • Beneficiary and sum assured changes are delivered as policy_altered. The underlying event types policy_beneficiaries_updated and policy_sum_assured_updated are exposed with type: "policy_altered" and an alteration field of beneficiaries_updated or sum_assured_updated.
  • Applying an alteration never records policy_updated. An alteration is always a single policy_alteration_package_applied event.
  • Document events are asynchronous. policy_schedule_updated, policy_terms_updated, policy_certificate_updated and policy_welcome_letter_updated are recorded after the triggering event completes and can interleave with other events.
  • The policy_anniversary webhook subscription is not currently emitted. Do not rely on it.
  • The events endpoint is not paginated. Use the created_at and version filters when retrieving long histories.
  • Keys without a value are omitted. Absence of a field on an event does not mean it was cleared. Use field_changes when you need to know that a value was removed.

Did this page help you?