Lifecycle hooks

Automate actions for existing policies triggered by predefined events

Overview

Lifecycle hooks allow you to run custom logic and specify actions to be performed by the platform when certain predefined events in the lifecycle of a policy are triggered.

These lifecycle events include when payments succeed or fail on a policy, when a policy is reactivated, when a claim is approved, and many more touch points. Within these hooks, actions can be queued to execute, such as updating the policy and its module data, changing the policy's status, crediting or debiting the premium ledger of the policy, or triggering a custom notification.

Each lifecycle hook is specified using a corresponding function in the product module code. This allows you not only to link a set of actions to a specific policy event, but also to define complex logic to configure the conditions under which each action should be queued.

Function definition

You need to specify the following parts of the lifecycle hook function.

  • Name - The name of the function determines which policy lifecycle event it will "hook into". The name must match one of the supported lifecycle hooks.
  • Parameters - Each lifecycle hook accepts a single params object as an argument. The policy object and policyholder object are passed to all lifecycle hooks as properties of params. (Note: this guide refers to the properties of params as the function parameters.) Certain lifecycle hooks also accept additional arguments related to the event that triggered the hook.
  • Body - The body of the function can be used to define custom logic specifying the conditions under which certain actions are returned.
  • Return statement - To execute an action or actions, the function must return an array of action objects, which are queued to be executed by the platform. See the full list of actions guide. (The return statement can be omitted if no actions are required, for example if the function only throws an error when certain conditions are met.)
const afterPolicyIssued = (params) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

Example

The example below uses the afterPolicyIssued hook to define custom logic for whether a policy is activated on issue. If the next policy billing date is within 30 days of the policy issue date, the policy is activated directly after it is issued. Otherwise, the policy will be issued with the default status of pending_initial_payment.

const afterPolicyIssued = ({ policy, policyholder }) => {
  const now = moment();
  const dayNumber = now.date();
  const billingDay = policy.billing_day;

  let nextBillingDate;
  if (billingDay < dayNumber) {
    nextBillingDate = now.clone().add(1, 'month').date(billingDay);
  } else {
    nextBillingDate = now.clone().date(billingDay);
  }

  // If next billing day is in less than 30 days, activate the policy
  if (!(nextBillingDate.diff(now, 'days') <= 30)) {
    return;
  }
  return [
    {
      name: 'activate_policy',
    },
  ];
};

Policy

These hooks are triggered by policy changes.

After policy issued

This hook is triggered when a policy is issued. Policies are issued via the policy issue endpoint. This endpoint can also be called by frontend users via the Root management dashboard.

const afterPolicyIssued = ({ policy, policyholder }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After policy activated

This hook is triggered when a policy is activated. Policies are activated through varying events.

const afterPolicyActivated = ({ policy, policyholder }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After policy not taken up

If not taken up checks are enabled in the general product settings, a policy's status will automatically be changed to not_taken_up if:

  • no payments have been collected successfully for that policy, and
  • the period of time since the last failed payment exceeds the specified grace period.

A policy can also be marked as not taken up by returning the mark_policy_not_taken_up action in the product module code.

const afterPolicyNotTakenUp = ({ policy, policyholder }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After policy lapsed

A policy lapses according to the lapse rules defined in the general product settings. The policy status will be changed to lapsed if:

  • at least one payment has been collected successfully since the policy was issued, and
  • the period of time since the last failed payment exceeds the grace period defined for the policy.

Policies can also be lapsed via the lapse policy endpoint, or by returning the lapse_policy action in the product module code.

const afterPolicyLapsed = ({ policy, policyholder }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After policy cancelled

Policies are cancelled via the cancel policy endpoint. This changes the policy status to cancelled. A policy cancellation can also be initiated from the Root management dashboard.

const afterPolicyCancelled = ({ policy, policyholder }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After policy expired

Policies are expired when they reach the end date on a policy. This changes the policy status to expired.

const afterPolicyExpired = ({ policy, policyholder }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

Before policy reactivated

Reactivating a policy means changing the status of an inactive policy (for example a cancelled or lapsed policy) to active. Reactivation can be enabled or disabled in the general product settings.

If reactivation has been enabled, policies can be reactivated via the reactivate policy endpoint. Reactivation can also be initiated by frontend users from the Root management dashboard.

Since this hook is triggered before the policy status is changed, it can be used to prevent reactivation if certain conditions are not met. This is achieved by throwing an Error from within the function, which will prevent reactivation from being completed.

The function takes both the policy object and the reactivationOption object as parameters. Read more in the reactivation hook guide.

const beforePolicyReactivated = ({ policy, policyholder, reactivationOption }) => {
  if (<condition under which reactivation should be prevented>) {
  	throw new Error('This policy cannot be reactivated');
  }
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After policy reactivated

This lifecycle hook is analogous to beforePolicyReactivated, except that it is triggered after policy reactivation has been completed.

const afterPolicyReactivated = ({ policy, policyholder, reactivationOption }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

Before policy start date changed

The start date of a policy can be changed via the Root management dashboard, if this feature is enabled in the product module settings. Changing the start date of a policy means that the billing history will be recalculated with respect to the new start date.

This hook can be used to cancel the change in start date if certain conditions are not met. This is achieved by throwing an Error from within the function, which will prevent start date change from being implemented.

The function takes both the policy object and the billingChange object as parameters.

const beforePolicyStartDateChanged = ({ policy, policyholder, billingChange }) => {
  if (<condition under which start date change should be prevented>) {
  	throw new Error(`This policy's start date cannot be changed`);
  }
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

Example of the billingChange object:

{
  "defer_update": false,
  "billing_day": 1,
  "start_date": "2022-01-01T00:00:00.000Z",
  "ledger_adjustments": [
    {
      "policy_id": "0a4c9044-856b-4584-a0d0-5ba97d44d124",
      "organization_id": "00000000-0000-0000-0000-000000000001",
      "environment": "sandbox",
      "amount": -17041,
      "description": "Adjustment for start date change",
      "currency": "ZAR"
    }
  ],
  "current_balance": -998,
  "monthly_premium": 2577
}

The billingChange object has the following properties:

PropertyDefinition
start_datestring. The new start date for the policy.
billing_day
optional
number. The new billing day for the policy.
defer_updateboolean. Specifies whether the new start date will be applied to the policy immediately, or deferred to a later date.
defer_update_until
optional
string. The date on which the start date change will be applied, if the policy start date is going to be deferred.
billing_month
optional
number. The new billing day for the policy if it is an annual policy.
ledger_adjustmentsarray. An array of ledger entries that will be added to to policy after the start date changes. Please see the "Ledger adjustments" table.
current_balancenumber. The current policy balance in cents.
monthly_premiumnumber. The policy monthly premium in cents.

Each object in the ledger_adjustments array has the following properties:

PropertyDefinition
policy_idstring. The UUID of the policy to which the ledger adjustment applies.
organization_idstring. The UUID of the organisation under which the policy was issued.
environmentstring. One of ]
{
"data": {
"h-0".
amountinteger. The amount of the ledger adjustment in cents.
description
optional
string. The reason for the ledger adjustment.
currencystring. The three-digit code of the currency in which the policy is billed (e.g. USD).
balance
optional
integer. The resulting balance on the policy ledger, in cents.

After policy start date changed

This lifecycle hook is analogous to beforePolicyStartDateChanged, except that it is triggered after policy start date has been changed. This hook can be used to generate any side-effects, such as making further updates to the policy module data as a result of the start date change.

const afterPolicyStartDateChanged = ({ policy, policyholder, billingChange }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After alteration package applied

This lifecycle hook is triggered after an alteration package is applied to a policy. To see which properties are included on the alteration package object, go to the API reference for the "Apply alteration package to policy" endpoint and click on the "Success" response.

Read more about how alteration hooks are configured in the alteration hooks guide.

const afterAlterationPackageApplied = ({
  policy,
  policyholder,
  alteration_package,
  alteration_hook_key,
}) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

Payments

These hooks are triggered by payment events, such as a successful payment collection, a payment collection failing, or a previous payment being reversed. Read more about how these payment events are defined in the billing and payments guide (work in progress).

The payment event hooks accept a policy object and a payment object as arguments.

After payment success

A payment is marked with a successful status five days after the payment was submitted, provided that Root has not received a failed payment update from the payments provider within those five days.

const afterPaymentSuccess = ({ policy, policyholder, payment }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After payment failed

A payment is marked with a failed status if Root receives a notification, saying that the payment failed to be collected, from the payments provider within five days of the payment being submitted.

If the failed payment notification is received after five days from the payment submission date, the original payment is still marked as successful. To correct the outstanding balance on the policy, a payment reversal is created to debit the policy's ledger with the original payment amount.

const afterPaymentFailed = ({ policy, policyholder, payment }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After payment reversed

A payment reversal is a completely new, unique payment object that negates a previous payment amount. It will have a payment_type of reversal, and a field called reversal_of_payment_id which stores a reference to the original (reversed) payment. The payment amount on the payment reversal will be negative.

This payment object representing the reversal is passed to the afterPaymentReversed() function, together with the policy object.

const afterPaymentReversed = ({ policy, policyholder, payment }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

Payment coupons

Payment coupons allow you to credit a policy ledger for scenarios such as reward programs or schedule payment holidays for policies. For more information on creating coupons, have a look at the API.

Before payment coupon created

There is often a need for rules to govern when payment coupons may be created for a policy.

Since this hook is triggered before the payment coupon is created, it can be used to prevent the coupons from being created if certain conditions are not met. This is achieved by throwing an Error from within the function.

The function takes both the policy object and the newPaymentCoupons array of coupons as parameters.

const beforePaymentCouponCreated = ({ policy, policyholder, newPaymentCoupons }) => {
  // Custom logic
  if (<condition under which creating payment coupons should be prevented>) {
  	throw new Error(`Payment coupons cannot be created for this policy`);
  }
  return [ ... ]; // Optional list of actions to queue
}

After payment coupon created

After a payment coupon has been created, the payment coupon is passed to the afterPaymentCouponCancelled function, together with the policy object.

const afterPaymentCouponCreated = ({ policy, policyholder, paymentCoupons }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After payment coupon cancelled

After a payment coupon has been canceled, the payment coupon is passed to the afterPaymentCouponCancelled function, together with the policy object.

Payment coupons are canceled via the cancel payment coupon endpoint (for a single payment coupon) or the cancel payment coupons endpoint (to cancel all payment coupons on a policy). Payment coupons can also be canceled from the Root management dashboard.

const afterPaymentCouponCancelled = ({ policy, policyholder, paymentCoupons }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After payment coupon redeemed

After a payment coupon has been redeemed, the payment coupon is passed to the afterPaymentCouponRedeemed function, together with the policy object.

const afterPaymentCouponRedeemed = ({ policy, policyholder, paymentCoupons }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After payment coupon reversed

Payment coupons can be reversed, changing their status from pending to reversed.

After a payment coupon has been reversed, the payment coupon is passed to the afterPaymentCouponReversed function, together with the policy object.

const afterPaymentCouponReversed = ({ policy, policyholder, paymentCoupons }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

Claims

These hooks are triggered by events related to claims. Read more about claims and how claims blocks work in the claims guide.

The claim hook functions accept the policy object linked to the claim, and the claim object itself as arguments.

After policy linked to claim

A claim is linked to a policy via the link a policy endpoint.

const afterPolicyLinkedToClaim = ({ policy, policyholder, claim }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After claim block updated

Claim information is stored in claims blocks, and specifically the claim block state. Read more about how claims block states work in the claims guide.

These blocks can be updated either via the update a block state endpoint (for a single block), or the update multiple block states endpoint. Claim blocks can also be updated directly in the user interface on the Root management dashboard.

const afterClaimBlockUpdated = ({ policy, policyholder, claim }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

❗️

Avoid infinite loops in claims blocks

The afterClaimBlockUpdated hook is triggered by a change of state of any of the blocks defined in the claim blocks schema. This means an infinite loop can be created unintentionally by updating a claim block state via the API from within the afterClaimBlockUpdated() function.

Updating claim block states from within the afterClaimBlockUpdated() function should be avoided where possible. If this cannot be avoided, you will need to define the logic in the body of the afterClaimBlockUpdated() function carefully to prevent an infinite loop from being created.

You can verify that no infinite loop has been created by checking the Execution Logs under the Product Module inside the Workbench home on the Dashboard. To stop an infinite loop in progress, you can close the claim.

After claim sent to review

After the claim is sent to review with the assessor (send-to-review), this lifecycle hook is triggered.

const afterClaimSentToReview = ({ policy, policyholder, claim }) => {
  // Custom logic
  return [ ... ]; // Optional list of actions to queue
}

After claim decision

After the claim goes to the assessor for a decision (send-to-review) and a decision is made (approve, repudiate, goodwill, no claim), this lifecycle hook is triggered.

To know which decision was made by the assesor, you can reference claim.approval_status, which will be one of ["approved", "repudiated", "goodwill", "no_claim"].

const afterClaimDecision = ({ policy, policyholder, claim }) => {
  if (claim.approval_status === 'approved') {
    // Custom logic
    return [ ... ]; // Optional list of actions to queue
  }
}

After claim decision acknowledged

After the claim assessor decision (approve, repudiate, goodwill, no claim), the claim is sent to the claims supervisor for review. If the decision is acknowledged (acknowledge approved, acknowledge repudiated, acknowledge goodwill, or acknowledge no claim) by the claims supervisor, this lifecycle hook is triggered.

To know which decision was acknowledged by the supervisor, you can reference claim.approval_status, which will be one of ["approved", "repudiated", "goodwill", "no_claim"].

const afterClaimDecisionAcknowledged = ({ policy, policyholder, claim }) => {
  if (claim.approval_status === 'approved') {
    // Custom logic
    return [ ... ]; // Optional list of actions to queue
  }
}

After claim closed

After the claim is closed (close), this lifecycle hook is triggered.

const afterClaimClosed = ({ policy, policyholder, claim }) => {
    // Custom logic
  return [ ... ]; // Optional list of actions to queue
}