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.
| Field | Type | Description |
|---|---|---|
policy_id | string | The policy the event belongs to. |
type | string | The event type, for example policy_issued. See Full event type reference. |
event_version | number | The event's sequence number within the policy. Increases by one per event. Use it to order events on the same policy. |
created_at | string | ISO 8601 timestamp at which the event was recorded. |
requested_by | object | Who caused the event: { "type": "...", "id": "..." }. See the table below. |
metadata | object | Request tracing metadata. Includes requested_by and requested_at. |
field_changes | array | Optional. A list of { "path", "before", "after" } entries describing exactly which policy fields the event changed. See below. |
requested_by.type
requested_by.type| Value | Meaning |
|---|---|
api_key | An API call authenticated with an API key. id is the API key ID. |
user | A user acting in the dashboard. id is the user ID. |
system | A scheduled or automated platform process, for example the lapse run. |
product_module | An action returned by product module code. |
embed_jwt | A call made from an embedded customer flow. |
collection_module | A collection module. |
collection_module_token | A call authenticated with a collection module token. |
Aboutfield_changes
field_changesis a precise diff of the policy before and after the event. Each entry has a dot-separatedpathin the policy's internalcamelCasefield naming (for examplemonthlyPremiumormodule.vehicleValue), the valuebefore, and the valueafter. A field that was added has nobefore; a field that was removed has noafter.
- 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:
| Field | Description |
|---|---|
webhook_id | The ID of the webhook configuration that produced the delivery. |
environment | sandbox or production. |
verification_token | Your configured verification token, if any. |
event | The policy event, in the envelope described above. |
policy | The 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
policy_issued— the full initial state of the policy.text_note_created— one per note carried over from the application, if any.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).payment_method_assigned— if a payment method was supplied on the application.- async
policy_terms_updated,policy_schedule_updated,policy_welcome_letter_updatedandpolicy_certificate_updatedas 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.
policy_alteration_package_applied- async
policy_schedule_updated, andpolicy_certificate_updatedwhere the product issues certificates.
| Field | Type | Description |
|---|---|---|
alteration_package_id | string | The package that was applied. Retrieve it with the alteration package endpoints for input_data and change_description. |
alteration_key | string | The key of the alteration hook that produced the package, as defined in your product module. |
alteration_name | string | The display name of the alteration hook. |
package_name | string | The policy's package name after the alteration. |
sum_assured | integer | The sum assured after the alteration, in cents. |
monthly_premium | integer | The monthly premium after the alteration, in cents. See Premium fields on events. |
base_premium | integer | The base premium after the alteration, in cents. |
billing_amount | integer | The amount that will be billed at the next billing run, in cents. Set equal to monthly_premium on apply. |
billing_frequency | string | Present when the product allows billing frequency changes. |
billing_month | integer | Present alongside billing_frequency. |
start_date, end_date | string | Present when the alteration changed them. |
module | object | The product-specific policy data after the alteration. |
restricted_data | object | Restricted policy data after the alteration, if the hook returned it. |
charges | array | The policy's charges after the alteration, if the hook returned them. See Charges and commissions. |
field_changes | array | The diff between the policy before and after the alteration. |
Cancellation and reactivation
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.
| Field | Type | Description |
|---|---|---|
reason | string | Free-text reason supplied on cancellation. |
cancellation_requestor | string | insurer or client. |
cancellation_type | string | The 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_reason | string | On 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.
| Event | When |
|---|---|
policy_lapsed | The policy lapsed under your product's lapse rules. |
policy_lapse_failed | A scheduled lapse could not be actioned on its action date. Carries failure_reason. |
policy_expired | The policy reached its end_date. |
policy_not_taken_up | The policy was marked not taken up, automatically or manually. |
policy_lapse_and_ntu_prevented | Lapse and not-taken-up processing was suspended for the policy for a period. |
policy_lapse_and_ntu_resumed | The suspension ended. |
Other policy updates
| Event | What changed |
|---|---|
policy_updated | One or more top-level policy fields changed outside of an alteration. See Interpreting policy_updated. |
policy_premium_updated | The premium was recalculated from the policy's covered items. Carries monthly_premium. |
policy_billing_updated | The billing amount was set directly. Carries billing_amount. |
policy_altered | The 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_requoted | The policy was re-rated from a new quote. |
policy_bumped | The policy was moved to a newer product module definition. |
Payment methods and billing instruments
| Event | What changed |
|---|---|
payment_method_assigned | A payment method was attached. Carries payment_method_id. |
payment_method_removed | The payment method was detached. Carries payment_method_id. |
policy_debicheck_mandate_updated | A DebiCheck mandate was linked or unlinked. Carries debi_check_mandate_id. |
Full event type reference
| Group | Event types |
|---|---|
| Lifecycle and status | policy_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 |
| Changes | policy_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 methods | payment_method_added_event, payment_method_assigned, payment_method_removed, policy_debicheck_mandate_updated, policy_receipt_created |
| Documents | policy_terms_updated, policy_schedule_updated, policy_welcome_letter_updated, policy_certificate_updated, policy_anniversary |
| Notes and attachments | text_note_created, note_pinned, note_unpinned, note_pins_reordered, note_content_overridden, attachment_created, attachment_archived |
| Links | claim_linked, complaint_linked |
| Invoices | invoice_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_updatedpolicy_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 present | requested_by.type | Cause |
|---|---|---|
Only policyholder_id | any | The policy was reassigned to a different policyholder. |
Only billing_day | same as the adjacent payment_method_assigned | A 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. |
| Any | product_module | An update_policy action returned by a lifecycle hook or scheduled function. |
app_data, billing_day, billing_month, start_date, end_date | api_key, user, embed_jwt | The 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 bypolicy_updatedThe policy's
chargesbreakdown is set by the policy issue hook and by alteration hooks. A change to charges therefore appears onpolicy_alteration_package_applied(in itschargesfield and infield_changesundercharges.*), and on thepolicyobject attached to webhook deliveries. Product-specific values your module stores undermoduleorrestricted_datacan change through eitherpolicy_updatedorpolicy_alteration_package_applied;field_changestells 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:
- If
field_changesis present, look for an entry withpathequal tomonthlyPremium. Its absence means the premium did not change. - Otherwise, compare the event's
monthly_premiumto the policy'smonthly_premiumimmediately before the event. That is themonthly_premiumon the most recent earlier event that carries one (policy_issued,policy_alteration_package_applied,policy_updated,policy_premium_updated,policy_members_updatedorpolicy_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_premiumis always the new recurring amountOn every event that carries it,
monthly_premiumis 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.
| Field | Meaning |
|---|---|
monthly_premium | The total monthly premium in cents that will be billed to the policyholder going forward. |
base_premium | The minimum allowed monthly premium in cents, including risk pricing and platform fees, before any product-specific loadings or discounts. |
billing_amount | The 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_frequency | monthly, 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_assured | The 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
| Key | Links to |
|---|---|
policy_id | The policy. Present on every event. |
event_version | Ordering within one policy. Consecutive integers; a gap means you have missed an event. |
created_at | Ordering across policies. |
alteration_package_id on policy_alteration_package_applied | The 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_issued | The application the policy was issued from, the policyholder, and the initial payment method. |
payment_method_id on payment_method_assigned / payment_method_removed | The payment method. |
requested_by.id | The user, API key or other principal that caused the event. |
policy object on a webhook delivery | The 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_updatedwebhook subscription also delivers payment method removals. When a payment method is removed, the platform sends thepayment_method_removedevent to both thepolicy_updatedandpayment_method_removedsubscriptions. Both deliveries carryevent.type: "payment_method_removed". If you subscribe to both, de-duplicate onpolicy_idandevent_version.- Beneficiary and sum assured changes are delivered as
policy_altered. The underlying event typespolicy_beneficiaries_updatedandpolicy_sum_assured_updatedare exposed withtype: "policy_altered"and analterationfield ofbeneficiaries_updatedorsum_assured_updated.- Applying an alteration never records
policy_updated. An alteration is always a singlepolicy_alteration_package_appliedevent.- Document events are asynchronous.
policy_schedule_updated,policy_terms_updated,policy_certificate_updatedandpolicy_welcome_letter_updatedare recorded after the triggering event completes and can interleave with other events.- The
policy_anniversarywebhook subscription is not currently emitted. Do not rely on it.- The events endpoint is not paginated. Use the
created_atandversionfilters 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_changeswhen you need to know that a value was removed.
Updated about 5 hours ago