Notification Plugin Overview

Sometimes, you may need to perform some action based on what happens in Kill Bill. For example, you may need to send an email when an invoice is generated, or update SalesForce when an account is created. Creating your own custom notification plugin is the answer to such scenarios. Notification plugins listen to Kill Bill events and can take appropriate action. This article explains how you can go about creating your custom notification plugin. Such plugins can also be a great alternative to Push Notifications in performance demanding environments.

How to Develop a Custom Notification Plugin

We provide a simple Hello World Plugin that can be used as the starting point to develop your custom notification plugin. You can refer to our Plugin Development document in order to understand how to set it up.

The Hello World Plugin has a class You can create a class similar to this to write your plugin code. Let us take a closer look at the HelloWorldListener class:

public class HelloWorldListener implements OSGIKillbillEventDispatcher.OSGIKillbillEventHandler{
    private static final Logger logger = LoggerFactory.getLogger(HelloWorldListener.class);
    private final OSGIKillbillAPI osgiKillbillAPI;
    public HelloWorldListener(final OSGIKillbillAPI killbillAPI) {
        this.osgiKillbillAPI = killbillAPI;
    public void handleKillbillEvent(final ExtBusEvent killbillEvent) {"Received event {} for object id {} of type {}",
        final TenantContext context = new PluginTenantContext(killbillEvent.getAccountId(), killbillEvent.getTenantId());
        switch (killbillEvent.getEventType()) {
            // Handle ACCOUNT_CREATION and ACCOUNT_CHANGE only for demo purpose and just print the account
            case ACCOUNT_CREATION:
            case ACCOUNT_CHANGE:
                try {
                    final Account account = osgiKillbillAPI.getAccountUserApi().getAccountById(killbillEvent.getAccountId(), context);
          "Account information: " + account);
                } catch (final AccountApiException e) {
                    logger.warn("Unable to find account", e);
            // Nothing
  • We provide an interface called OSGIKillbillEventDispatcher.OSGIKillbillEventHandler. In order to create your own notification plugin, you need to create a class that implements this interface and implement the public void handleKillbillEvent(final ExtBusEvent killbillEvent) method as done in the code above

  • The killbillEvent parameter passed to the handleKillbillEvent method can be used to obtain the event type. The event type is just an enum constant defined in ExtBusEventType and defines all the events that can be handled by the notification plugin. These events are explained in detail in the Kill Bill Events document. Line 14 specifies a switch statement that checks the event type and takes different actions for different events. You can write similar code to handle the desired event.

  • Kill Bill provides a class called This class exposes all of Kill Bill’s internal APIs. An object of this class will automatically be available in your plugin. Line 3 declares a field corresponding to osgiKillbillAPI which is intialized via the constructor at Line 5

  • The osgiKillbillAPI object can then be used to fetch the appropriate api object. Line 22 uses osgiKillbillAPI.getAccountUserApi() to obtain an AccountUserApi object

  • The API object that then be used to retrieve the appropriate information from Kill Bill. For example, the AccountUserApi has several methods that can be used to retrieve account related information. Line 22 invokes AccountUserApi#getAccountById

  • Note that in order to invoke write API operations like AccountUserApi#createAccount , the code must authenticate against Kill Bill first. Otherwise, it will result in an org.apache.shiro.authz.UnauthenticatedException as explained in the plugin development document here. In order to authenticate against Kill Bill, the following code needs to be added before invoking any API operation:

    killbillAPI.getSecurityApi().login(login, password);
  • The AccountUserApi#getAccountById method requires as parameter a TenantContext. So, Line 13 creates a TenantContext object with the account id and tenant id. This TenantContext object is passed to the AccountUserApi#getAccountById method at Line 22

  • In general, all the API operations require either a TenantContext or a CallContext object. A TenantContext is mandatory for read only operations (operations which retrieve some data from Kill Bill) like the AccountApi#getAccountById used above. A CallContext is mandatory for read/write operations and contains additional metadata (user doing the change, reason code, etc.). These values are used for auditing purposes.

  • A TenantContext should always have the tenantId populated. Whenever possible, the accountId should also be populated (the exception is when an operation isn’t really tied to any account, like TagUserApi#getTagDefinitions API: these tag definitions are defined at the tenant level, not at the account level).

Some Common Use Cases for Creating a Notification Plugin

This section lists some common use cases where you can use the notification plugin and also specifies how you can use the notification plugin in these scenarios.

Doing something when an account is created/modified

Sometimes, you may wish to perform some action like updating Salesforce when there is some account related activity. For this, you can write a custom notification plugin that handles the following events:



You can obtain the account information from Kill Bill as follows:

Account account = osgiKillbillAPI.getAccountUserApi().getAccountById(killbillEvent.getAccountId(), context)

You can then use this account object to update the necessary information in Salesforce.

Existing Notification Plugins

We already provide some notification plugins for some common scenarios. The following are some of the existing notification plugins:

  1. Email Notification Plugin - This plugin listens to certain events and notifies customers through emails. See Email Notification Plugin guide.

  2. Analytics Plugin - This plugin can be used to generate various reports. See Analytics Plugin Tutorial.

  3. Kpm - Kpm is a special notification plugin which is used for managing other plugins


Sometimes an exception might occur in your notification plugin due to which it might not be able to handle the event sent to it by Kill Bill. By default, if a plugin triggers a runtime exception, Kill Bill dispatches the event right away up to 3 times (or as configured by the org.killbill.notificationq.external.max.failure.retry global property which can be configured as explained in the Configuration Guide). However, in some cases, you may want Kill Bill to retry sending the event again at a later time (if for example a third-party provider is down). To do so, your plugin can throw a NotificationPluginApiRetryException to include its own retry schedule. The retry schedule should include a Period array, each element in the array should specify the duration after which the retry should be attempted.

For example, consider the following exception:

// Retry in an hour and in 24hrs
throw new NotificationPluginApiRetryException(Arrays.asList(new Period[]{Period.hours(1), Period.days(1)}));

This specifies that Kill Bill should retry sending the event two times. The first should be an hour from now, while the second should be 24 hours from now.

When the NotificationPluginApiRetryException is caught by Kill Bill, the system computes the next retry date based on the schedule specified in the exception and the number of times that specific event has been retried.

So, in terms of responsibilities:

  • Plugin is in charge of deciding whether a NotificationPluginApiRetryException should be thrown and attach the associated retry schedule to it.

  • Kill Bill manages the retry logic and also keeps count of # existing retries versus retry schedule.

It is expected that the plugin will simply pass the same retry schedule for each retry iteration, but this is not enforced and left for the plugin to decide. Kill Bill will look at the most recent retry schedule attached to the exception currently being handled and determine what to do based on that. If for instance a first schedule included 2 retries 10 days apart, and then upon retrying one time, the new schedule now includes only 1 retry, the cycle of retries would end there (as the latest schedule contains only one retry and Kill Bill already retried one time). Because of such behavior, any plugin can trigger retries at any time: it is hence important that your listener is idempotent.