make action's node.js runtime version configurable (#178)

diff --git a/.gitignore b/.gitignore
index 3af456f..4891796 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,8 @@
 .gradle
 build/
 node_modules/
-action/*.zip
+actions/event-actions/*.zip
+actions/event-actions/package.json
 .idea
 *.iml
 
diff --git a/Dockerfile b/Dockerfile
index aa9ae23..2df3aac 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -14,8 +14,8 @@
   apt-get install -y nodejs
 
 # only package.json
-ADD package.json /cloudantTrigger/
-RUN cd /cloudantTrigger; npm install
+ADD package.json /
+RUN cd / && npm install --production
 
 # App
 ADD provider/. /cloudantTrigger/
diff --git a/actions/changes.js b/actions/changes.js
deleted file mode 100644
index 012bf90..0000000
--- a/actions/changes.js
+++ /dev/null
@@ -1,97 +0,0 @@
-var request = require('request');
-
-function main(msg) {
-
-    let eventMap = {
-        CREATE: 'post',
-        READ: 'get',
-        UPDATE: 'put',
-        DELETE: 'delete'
-    };
-    // for creation -> CREATE
-    // for reading -> READ
-    // for updating -> UPDATE
-    // for deletion -> DELETE
-    var lifecycleEvent = msg.lifecycleEvent;
-
-    var endpoint = msg.apihost;
-    var webparams = createWebParams(msg);
-
-    var url = `https://${endpoint}/api/v1/web/whisk.system/cloudantWeb/changesWebAction.http`;
-
-    if (lifecycleEvent in eventMap) {
-        var method = eventMap[lifecycleEvent];
-        return requestHelper(url, webparams, method);
-    } else {
-        return Promise.reject('unsupported lifecycleEvent');
-    }
-}
-
-function requestHelper(url, input, method) {
-
-    return new Promise(function(resolve, reject) {
-
-        var options = {
-            method : method,
-            url : url,
-            json: true,
-            rejectUnauthorized: false
-        };
-
-        if (method === 'get') {
-            options.qs = input;
-        } else {
-            options.body = input;
-        }
-
-        request(options, function(error, response, body) {
-
-            if (!error && response.statusCode === 200) {
-                resolve(body);
-            }
-            else {
-                if (response) {
-                    console.log('cloudant: Error invoking whisk action:', response.statusCode, body);
-                    reject(body);
-                }
-                else {
-                    console.log('cloudant: Error invoking whisk action:', error);
-                    reject(error);
-                }
-            }
-        });
-    });
-}
-
-function createWebParams(rawParams) {
-    var namespace = process.env.__OW_NAMESPACE;
-    var triggerName = ':' + namespace + ':' + parseQName(rawParams.triggerName, '/').name;
-
-    var webparams = Object.assign({}, rawParams);
-    delete webparams.lifecycleEvent;
-    delete webparams.bluemixServiceName;
-    delete webparams.apihost;
-
-    webparams.triggerName = triggerName;
-
-    return webparams;
-}
-
-function parseQName(qname, separator) {
-    var parsed = {};
-    var delimiter = separator || ':';
-    var defaultNamespace = '_';
-    if (qname && qname.charAt(0) === delimiter) {
-        var parts = qname.split(delimiter);
-        parsed.namespace = parts[1];
-        parsed.name = parts.length > 2 ? parts.slice(2).join(delimiter) : '';
-    } else {
-        parsed.namespace = defaultNamespace;
-        parsed.name = qname;
-    }
-    return parsed;
-}
-
-exports.main = main;
-
-
diff --git a/actions/changesWebAction.js b/actions/changesWebAction.js
deleted file mode 100644
index 1bade20..0000000
--- a/actions/changesWebAction.js
+++ /dev/null
@@ -1,494 +0,0 @@
-var request = require('request');
-var moment = require('moment');
-
-function main(params) {
-
-    if (!params.authKey) {
-        return sendError(400, 'no authKey parameter was provided');
-    }
-    if (!params.triggerName) {
-        return sendError(400, 'no trigger name parameter was provided');
-    }
-
-    var triggerParts = parseQName(params.triggerName);
-    var triggerID = `:${triggerParts.namespace}:${triggerParts.name}`;
-
-    var triggerURL = `https://${params.apihost}/api/v1/namespaces/${triggerParts.namespace}/triggers/${triggerParts.name}`;
-
-    var nano = require('nano')(params.DB_URL);
-    var db = nano.db.use(params.DB_NAME);
-    var workers = params.workers instanceof Array ? params.workers : [];
-
-    if (params.__ow_method === "post") {
-
-        // check for parameter errors
-        if (!params.dbname) {
-            return sendError(400, 'cloudant trigger feed: missing dbname parameter');
-        }
-        if (!params.host) {
-            return sendError(400, 'cloudant trigger feed: missing host parameter');
-        }
-        if (!params.username) {
-            return sendError(400, 'cloudant trigger feed: missing username parameter');
-        }
-        if (!params.password) {
-            return sendError(400, 'cloudant trigger feed: missing password parameter');
-        }
-
-        var query_params;
-
-        if (params.filter) {
-            query_params = params.query_params;
-            if (typeof queryParams === 'string') {
-                try {
-                    query_params = JSON.parse(params.query_params);
-                }
-                catch (e) {
-                    return sendError(400, 'The query_params parameter cannot be parsed. Ensure it is valid JSON.');
-                }
-            }
-            if (query_params && typeof query_params !== 'object') {
-                return sendError(400, 'The query_params parameter is not a valid JSON Object');
-            }
-        }
-        else if (params.query_params) {
-            return sendError(400, 'The query_params parameter is only allowed if the filter parameter is defined');
-        }
-
-        var newTrigger = {
-            id: triggerID,
-            host: params.host,
-            port: params.port,
-            protocol: params.protocol || 'https',
-            dbname: params.dbname,
-            user: params.username,
-            pass: params.password,
-            apikey: params.authKey,
-            since: params.since,
-            maxTriggers: params.maxTriggers || -1,
-            filter: params.filter,
-            query_params: query_params,
-            status: {
-                'active': true,
-                'dateChanged': Date.now()
-            }
-        };
-
-        return new Promise(function (resolve, reject) {
-            verifyTriggerAuth(triggerURL, params.authKey, false)
-            .then(() => {
-                return verifyUserDB(newTrigger);
-            })
-            .then(() => {
-                return getWorkerID(db, workers);
-            })
-            .then((worker) => {
-                console.log('trigger will be assigned to worker ' + worker);
-                newTrigger.worker = worker;
-                return createTrigger(db, triggerID, newTrigger);
-            })
-            .then(() => {
-                 resolve({
-                     statusCode: 200,
-                     headers: {'Content-Type': 'application/json'},
-                     body: {'status': 'success'}
-                 });
-            })
-            .catch(err => {
-                reject(err);
-            });
-        });
-
-    }
-    else if (params.__ow_method === "get") {
-        return new Promise(function (resolve, reject) {
-            verifyTriggerAuth(triggerURL, params.authKey, false)
-            .then(() => {
-                return getTrigger(db, triggerID);
-            })
-            .then(doc => {
-                var body = {
-                    config: {
-                        name: doc.id.split(':')[2],
-                        namespace: doc.id.split(':')[1],
-                        host: doc.host,
-                        port: doc.port,
-                        protocol: doc.protocol,
-                        dbname: doc.dbname,
-                        username: doc.user,
-                        password: doc.pass,
-                        since: doc.since,
-                        filter: doc.filter,
-                        query_params: doc.query_params,
-                    },
-                    status: {
-                        active: doc.status.active,
-                        dateChanged: moment(doc.status.dateChanged).utc().valueOf(),
-                        dateChangedISO: moment(doc.status.dateChanged).utc().format(),
-                        reason: doc.status.reason
-                    }
-                };
-                resolve({
-                    statusCode: 200,
-                    headers: {'Content-Type': 'application/json'},
-                    body: body
-                });
-            })
-            .catch(err => {
-                reject(err);
-            });
-        });
-    }
-    else if (params.__ow_method === "put") {
-        return new Promise(function (resolve, reject) {
-            verifyTriggerAuth(triggerURL, params.authKey, true)
-            .then(() => {
-                return getTrigger(db, triggerID);
-            })
-            .then(trigger => {
-                if (trigger.status && trigger.status.active === false) {
-                    reject(sendError(400, `${params.triggerName} cannot be updated because it is disabled`));
-                }
-                if (params.filter || params.query_params) {
-                    var updatedParams = {
-                        filter: trigger.filter,
-                        query_params: trigger.query_params
-                    };
-
-                    if (params.filter) {
-                        updatedParams.filter = params.filter;
-                    }
-                    if (params.query_params) {
-                        if (updatedParams.filter) {
-                            var query_params = params.query_params;
-                            if (typeof query_params === 'string') {
-                                try {
-                                    query_params = JSON.parse(params.query_params);
-                                }
-                                catch (e) {
-                                    reject(sendError(400, 'The query_params parameter cannot be parsed. Ensure it is valid JSON.'));
-                                }
-                            }
-                            if (typeof query_params !== 'object') {
-                                reject(sendError(400, 'The query_params parameter is not a valid JSON Object'));
-                            }
-                            updatedParams.query_params = query_params;
-                        } else {
-                            reject(sendError(400, 'The query_params parameter is only allowed if the filter parameter is defined'));
-                        }
-                    }
-                    return updateTrigger(db, triggerID, trigger, updatedParams);
-                } else {
-                    reject(sendError(400, 'At least one of filter or query_params parameters must be supplied'));
-                }
-            })
-            .then(() => {
-                resolve({
-                    statusCode: 200,
-                    headers: {'Content-Type': 'application/json'},
-                    body: {'status': 'success'}
-                });
-           })
-           .catch(err => {
-               reject(err);
-           });
-        });
-    }
-    else if (params.__ow_method === "delete") {
-
-        return new Promise(function (resolve, reject) {
-            verifyTriggerAuth(triggerURL, params.authKey, true)
-            .then(() => {
-                return disableTrigger(db, triggerID, 0);
-            })
-            .then(() => {
-                return deleteTrigger(db, triggerID, 0);
-            })
-            .then(() => {
-                resolve({
-                    statusCode: 200,
-                    headers: {'Content-Type': 'application/json'},
-                    body: {'status': 'success'}
-                });
-            })
-            .catch(err => {
-                reject(err);
-            });
-        });
-    }
-    else {
-        return sendError(400, 'unsupported lifecycle event');
-    }
-}
-
-function getWorkerID(db, availabeWorkers) {
-
-    return new Promise((resolve, reject) => {
-        var workerID = availabeWorkers[0] || 'worker0';
-
-        if (availabeWorkers.length > 1) {
-            db.view('triggerViews', 'triggers_by_worker', {reduce: true, group: true}, function (err, body) {
-                if (!err) {
-                    var triggersByWorker = {};
-
-                    availabeWorkers.forEach(worker => {
-                        triggersByWorker[worker] = 0;
-                    });
-
-                    body.rows.forEach(row => {
-                        if (row.key in triggersByWorker) {
-                            triggersByWorker[row.key] = row.value;
-                        }
-                    });
-
-                    // find which worker has the least number of assigned triggers
-                    for (var worker in triggersByWorker) {
-                        if (triggersByWorker[worker] < triggersByWorker[workerID]) {
-                            workerID = worker;
-                        }
-                    }
-                    resolve(workerID);
-                } else {
-                    reject(err);
-                }
-            });
-        }
-        else {
-            resolve(workerID);
-        }
-    });
-}
-
-function createTrigger(triggerDB, triggerID, newTrigger) {
-
-    return new Promise(function(resolve, reject) {
-
-        triggerDB.insert(newTrigger, triggerID, function (err) {
-            if (!err) {
-                resolve();
-            }
-            else {
-                reject(sendError(err.statusCode, 'error creating cloudant trigger.', err.message));
-            }
-        });
-    });
-}
-
-function getTrigger(triggerDB, triggerID) {
-
-    return new Promise(function(resolve, reject) {
-
-        triggerDB.get(triggerID, function (err, existing) {
-            if (err) {
-                reject(err);
-            } else {
-                resolve(existing);
-            }
-        });
-    });
-}
-
-function disableTrigger(triggerDB, triggerID, retryCount) {
-
-    return new Promise(function(resolve, reject) {
-
-        triggerDB.get(triggerID, function (err, existing) {
-            if (!err) {
-                var updatedTrigger = existing;
-                updatedTrigger.status = {'active': false};
-
-                triggerDB.insert(updatedTrigger, triggerID, function (err) {
-                    if (err) {
-                        if (err.statusCode === 409 && retryCount < 5) {
-                            setTimeout(function () {
-                                disableTrigger(triggerDB, triggerID, (retryCount + 1))
-                                .then(() => {
-                                    resolve();
-                                })
-                                .catch(err => {
-                                    reject(err);
-                                });
-                            }, 1000);
-                        }
-                        else {
-                            reject(sendError(err.statusCode, 'there was an error while marking the trigger for delete in the database.', err.message));
-                        }
-                    }
-                    else {
-                        resolve();
-                    }
-                });
-            }
-            else {
-                reject(sendError(err.statusCode, 'could not find the trigger in the database'));
-            }
-        });
-    });
-}
-
-function deleteTrigger(triggerDB, triggerID, retryCount) {
-
-    return new Promise(function(resolve, reject) {
-
-        triggerDB.get(triggerID, function (err, existing) {
-            if (!err) {
-                triggerDB.destroy(existing._id, existing._rev, function (err) {
-                    if (err) {
-                        if (err.statusCode === 409 && retryCount < 5) {
-                            setTimeout(function () {
-                                deleteTrigger(triggerDB, triggerID, (retryCount + 1))
-                                .then(resolve)
-                                .catch(err => {
-                                    reject(err);
-                                });
-                            }, 1000);
-                        }
-                        else {
-                            reject(sendError(err.statusCode, 'there was an error while deleting the trigger from the database.', err.message));
-                        }
-                    }
-                    else {
-                        resolve();
-                    }
-                });
-            }
-            else {
-                reject(sendError(err.statusCode, 'could not find the trigger in the database'));
-            }
-        });
-    });
-}
-
-function updateTrigger(triggerDB, triggerID, existing, params) {
-
-    return new Promise(function(resolve, reject) {
-        var message = 'Automatically disabled trigger while updating';
-        var status = {
-            'active': false,
-            'dateChanged': Date.now(),
-            'reason': {'kind': 'AUTO', 'statusCode': undefined, 'message': message}
-        };
-        existing.status = status;
-        triggerDB.insert(existing, triggerID, function (err) {
-            if (err) {
-                reject(sendError(err.statusCode, 'there was an error while disabling the trigger in the database.', err.message));
-            }
-            else {
-                resolve();
-            }
-        });
-    })
-    .then(() => {
-        return getTrigger(triggerDB, triggerID);
-    })
-    .then(trigger => {
-        for (var key in params) {
-            trigger[key] = params[key];
-        }
-        var status = {
-            'active': true,
-            'dateChanged': Date.now()
-        };
-        trigger.status = status;
-
-        return new Promise(function(resolve, reject) {
-            triggerDB.insert(trigger, triggerID, function (err) {
-                if (err) {
-                    reject(sendError(err.statusCode, 'there was an error while updating and re-enabling the trigger in the database.', err.message));
-                }
-                else {
-                    resolve();
-                }
-            });
-        });
-    });
-}
-
-function verifyTriggerAuth(triggerURL, authKey, isDelete) {
-    var auth = authKey.split(':');
-
-    return new Promise(function(resolve, reject) {
-
-        request({
-            method: 'get',
-            url: triggerURL,
-            auth: {
-                user: auth[0],
-                pass: auth[1]
-            },
-            rejectUnauthorized: false
-        }, function(err, response) {
-            if (err) {
-                reject(sendError(400, 'Trigger authentication request failed.', err.message));
-            }
-            else if(response.statusCode >= 400 && !(isDelete && response.statusCode === 404)) {
-                reject(sendError(response.statusCode, 'Trigger authentication request failed.'));
-            }
-            else {
-                resolve();
-            }
-        });
-    });
-}
-
-function verifyUserDB(triggerObj) {
-    var dbURL = `${triggerObj.protocol}://${triggerObj.user}:${triggerObj.pass}@${triggerObj.host}`;
-
-    // add port if specified
-    if (triggerObj.port) {
-        dbURL += ':' + triggerObj.port;
-    }
-
-    return new Promise(function(resolve, reject) {
-        try {
-            var nanoConnection = require('nano')(dbURL);
-            var userDB = nanoConnection.use(triggerObj.dbname);
-            userDB.info(function(err, body) {
-                if (!err) {
-                    resolve();
-                }
-                else {
-                    reject(sendError(err.statusCode, 'error connecting to database ' + triggerObj.dbname, err.message));
-                }
-            });
-        }
-        catch(err) {
-            reject(sendError(400, 'error connecting to database ' + triggerObj.dbname, err.message));
-        }
-
-    });
-}
-
-function sendError(statusCode, error, message) {
-    var params = {error: error};
-    if (message) {
-        params.message = message;
-    }
-
-    return {
-        statusCode: statusCode,
-        headers: { 'Content-Type': 'application/json' },
-        body: params
-    };
-}
-
-
-function parseQName(qname, separator) {
-    var parsed = {};
-    var delimiter = separator || ':';
-    var defaultNamespace = '_';
-    if (qname && qname.charAt(0) === delimiter) {
-        var parts = qname.split(delimiter);
-        parsed.namespace = parts[1];
-        parsed.name = parts.length > 2 ? parts.slice(2).join(delimiter) : '';
-    } else {
-        parsed.namespace = defaultNamespace;
-        parsed.name = qname;
-    }
-    return parsed;
-}
-
-
-exports.main = main;
-
-
diff --git a/actions/event-actions/changes.js b/actions/event-actions/changes.js
new file mode 100644
index 0000000..ddeffb5
--- /dev/null
+++ b/actions/event-actions/changes.js
@@ -0,0 +1,31 @@
+const common = require('./lib/common');
+
+function main(msg) {
+
+    let eventMap = {
+        CREATE: 'post',
+        READ: 'get',
+        UPDATE: 'put',
+        DELETE: 'delete'
+    };
+    // for creation -> CREATE
+    // for reading -> READ
+    // for updating -> UPDATE
+    // for deletion -> DELETE
+    var lifecycleEvent = msg.lifecycleEvent;
+
+    var endpoint = msg.apihost;
+    var webparams = common.createWebParams(msg);
+
+    var url = `https://${endpoint}/api/v1/web/whisk.system/cloudantWeb/changesWebAction.http`;
+
+    if (lifecycleEvent in eventMap) {
+        var method = eventMap[lifecycleEvent];
+        return common.requestHelper(url, webparams, method);
+    } else {
+        return Promise.reject('unsupported lifecycleEvent');
+    }
+}
+
+
+exports.main = main;
diff --git a/actions/event-actions/changesFeed_package.json b/actions/event-actions/changesFeed_package.json
new file mode 100644
index 0000000..d1738c6
--- /dev/null
+++ b/actions/event-actions/changesFeed_package.json
@@ -0,0 +1,5 @@
+{
+  "name": "changesFeed",
+  "version": "1.0.0",
+  "main": "changes.js"
+}
diff --git a/actions/event-actions/changesWebAction.js b/actions/event-actions/changesWebAction.js
new file mode 100644
index 0000000..af2b0e8
--- /dev/null
+++ b/actions/event-actions/changesWebAction.js
@@ -0,0 +1,272 @@
+const moment = require('moment');
+const common = require('./lib/common');
+const Database = require('./lib/Database');
+
+function main(params) {
+
+    if (!params.triggerName) {
+        return common.sendError(400, 'no trigger name parameter was provided');
+    }
+
+    var triggerParts = common.parseQName(params.triggerName);
+    var triggerData = {
+        apikey: params.authKey,
+        name: triggerParts.name,
+        namespace: triggerParts.namespace,
+        additionalData: common.constructObject(params.additionalData),
+    };
+    var triggerID = `:${triggerParts.namespace}:${triggerParts.name}`;
+
+    var workers = params.workers instanceof Array ? params.workers : [];
+    var db;
+
+    if (params.__ow_method === "post") {
+
+        // check for parameter errors
+        if (!params.dbname) {
+            return common.sendError(400, 'cloudant trigger feed: missing dbname parameter');
+        }
+        if (!params.host) {
+            return common.sendError(400, 'cloudant trigger feed: missing host parameter');
+        }
+        if (!params.username) {
+            return common.sendError(400, 'cloudant trigger feed: missing username parameter');
+        }
+        if (!params.password) {
+            return common.sendError(400, 'cloudant trigger feed: missing password parameter');
+        }
+
+        var query_params;
+
+        if (params.filter) {
+            query_params = params.query_params;
+            if (typeof queryParams === 'string') {
+                try {
+                    query_params = JSON.parse(params.query_params);
+                }
+                catch (e) {
+                    return common.sendError(400, 'The query_params parameter cannot be parsed. Ensure it is valid JSON.');
+                }
+            }
+            if (query_params && typeof query_params !== 'object') {
+                return common.sendError(400, 'The query_params parameter is not a valid JSON Object');
+            }
+        }
+        else if (params.query_params) {
+            return common.sendError(400, 'The query_params parameter is only allowed if the filter parameter is defined');
+        }
+
+        var newTrigger = {
+            id: triggerID,
+            host: params.host,
+            port: params.port,
+            protocol: params.protocol || 'https',
+            dbname: params.dbname,
+            user: params.username,
+            pass: params.password,
+            apikey: triggerData.apikey,
+            since: params.since,
+            maxTriggers: params.maxTriggers || -1,
+            filter: params.filter,
+            query_params: query_params,
+            status: {
+                'active': true,
+                'dateChanged': Date.now()
+            },
+            additionalData: triggerData.additionalData
+        };
+
+        return new Promise(function (resolve, reject) {
+            common.verifyTriggerAuth(triggerData, false)
+            .then(() => {
+                db = new Database(params.DB_URL, params.DB_NAME);
+                return verifyUserDB(newTrigger);
+            })
+            .then(() => {
+                return db.getWorkerID(workers);
+            })
+            .then((worker) => {
+                console.log('trigger will be assigned to worker ' + worker);
+                newTrigger.worker = worker;
+                return db.createTrigger(triggerID, newTrigger);
+            })
+            .then(() => {
+                resolve({
+                    statusCode: 200,
+                    headers: {'Content-Type': 'application/json'},
+                    body: {'status': 'success'}
+                });
+            })
+            .catch(err => {
+                reject(err);
+            });
+        });
+
+    }
+    else if (params.__ow_method === "get") {
+        return new Promise(function (resolve, reject) {
+            common.verifyTriggerAuth(triggerData, false)
+            .then(() => {
+                db = new Database(params.DB_URL, params.DB_NAME);
+                return db.getTrigger(triggerID);
+            })
+            .then(doc => {
+                var body = {
+                    config: {
+                        name: doc.id.split(':')[2],
+                        namespace: doc.id.split(':')[1],
+                        host: doc.host,
+                        port: doc.port,
+                        protocol: doc.protocol,
+                        dbname: doc.dbname,
+                        username: doc.user,
+                        password: doc.pass,
+                        since: doc.since,
+                        filter: doc.filter,
+                        query_params: doc.query_params,
+                    },
+                    status: {
+                        active: doc.status.active,
+                        dateChanged: moment(doc.status.dateChanged).utc().valueOf(),
+                        dateChangedISO: moment(doc.status.dateChanged).utc().format(),
+                        reason: doc.status.reason
+                    }
+                };
+                resolve({
+                    statusCode: 200,
+                    headers: {'Content-Type': 'application/json'},
+                    body: body
+                });
+            })
+            .catch(err => {
+                reject(err);
+            });
+        });
+    }
+    else if (params.__ow_method === "put") {
+
+        return new Promise(function (resolve, reject) {
+            var updatedParams = {};
+
+            common.verifyTriggerAuth(triggerData, false)
+            .then(() => {
+                db = new Database(params.DB_URL, params.DB_NAME);
+                return db.getTrigger(triggerID);
+            })
+            .then(trigger => {
+                if (trigger.status && trigger.status.active === false) {
+                    return reject(common.sendError(400, `${params.triggerName} cannot be updated because it is disabled`));
+                }
+
+                if (params.filter || params.query_params) {
+                    updatedParams.filter = trigger.filter;
+                    updatedParams.query_params = trigger.query_params;
+                }
+                else {
+                    return reject(common.sendError(400, 'At least one of filter or query_params parameters must be supplied'));
+                }
+
+                if (params.filter) {
+                    updatedParams.filter = params.filter;
+                }
+
+                if (params.query_params) {
+                    if (updatedParams.filter) {
+                        var query_params = params.query_params;
+                        if (typeof query_params === 'string') {
+                            try {
+                                query_params = JSON.parse(params.query_params);
+                            }
+                            catch (e) {
+                                return reject(common.sendError(400, 'The query_params parameter cannot be parsed. Ensure it is valid JSON.'));
+                            }
+                        }
+                        if (typeof query_params !== 'object') {
+                            return reject(common.sendError(400, 'The query_params parameter is not a valid JSON Object'));
+                        }
+                        updatedParams.query_params = query_params;
+                    }
+                    else {
+                        return reject(common.sendError(400, 'The query_params parameter is only allowed if the filter parameter is defined'));
+                    }
+                }
+                return db.disableTrigger(triggerID, trigger, 0, 'updating');
+            })
+            .then(triggerID => {
+                return db.getTrigger(triggerID);
+            })
+            .then(trigger => {
+                return db.updateTrigger(triggerID, trigger, updatedParams, 0);
+            })
+            .then(() => {
+                resolve({
+                    statusCode: 200,
+                    headers: {'Content-Type': 'application/json'},
+                    body: {'status': 'success'}
+                });
+            })
+            .catch(err => {
+                reject(err);
+            });
+        });
+    }
+    else if (params.__ow_method === "delete") {
+
+        return new Promise(function (resolve, reject) {
+            common.verifyTriggerAuth(triggerData, true)
+            .then(() => {
+                db = new Database(params.DB_URL, params.DB_NAME);
+                return db.getTrigger(triggerID);
+            })
+            .then(trigger => {
+                return db.disableTrigger(triggerID, trigger, 0, 'deleting');
+            })
+            .then(triggerID => {
+                return db.deleteTrigger(triggerID, 0);
+            })
+            .then(() => {
+                resolve({
+                    statusCode: 200,
+                    headers: {'Content-Type': 'application/json'},
+                    body: {'status': 'success'}
+                });
+            })
+            .catch(err => {
+                reject(err);
+            });
+        });
+    }
+    else {
+        return common.sendError(400, 'unsupported lifecycleEvent');
+    }
+}
+
+function verifyUserDB(triggerObj) {
+    var dbURL = `${triggerObj.protocol}://${triggerObj.user}:${triggerObj.pass}@${triggerObj.host}`;
+
+    // add port if specified
+    if (triggerObj.port) {
+        dbURL += ':' + triggerObj.port;
+    }
+
+    return new Promise(function(resolve, reject) {
+        try {
+            var nanoConnection = require('nano')(dbURL);
+            var userDB = nanoConnection.use(triggerObj.dbname);
+            userDB.info(function(err, body) {
+                if (!err) {
+                    resolve();
+                }
+                else {
+                    reject(common.sendError(err.statusCode, 'error connecting to database ' + triggerObj.dbname, err.message));
+                }
+            });
+        }
+        catch(err) {
+            reject(common.sendError(400, 'error connecting to database ' + triggerObj.dbname, err.message));
+        }
+
+    });
+}
+
+exports.main = main;
diff --git a/actions/event-actions/changesWeb_package.json b/actions/event-actions/changesWeb_package.json
new file mode 100644
index 0000000..f2b3fc6
--- /dev/null
+++ b/actions/event-actions/changesWeb_package.json
@@ -0,0 +1,5 @@
+{
+  "name": "changesWebAction",
+  "version": "1.0.0",
+  "main": "changesWebAction.js"
+}
diff --git a/actions/event-actions/lib/Database.js b/actions/event-actions/lib/Database.js
new file mode 100644
index 0000000..962b2df
--- /dev/null
+++ b/actions/event-actions/lib/Database.js
@@ -0,0 +1,195 @@
+const common = require('./common');
+
+// constructor for DB object - a thin, promise-loving wrapper around nano
+module.exports = function(dbURL, dbName) {
+    var nano = require('nano')(dbURL);
+    this.db = nano.db.use(dbName);
+    var utilsDB = this;
+
+    this.getWorkerID = function(availabeWorkers) {
+
+        return new Promise((resolve, reject) => {
+            var workerID = availabeWorkers[0] || 'worker0';
+
+            if (availabeWorkers.length > 1) {
+                utilsDB.db.view('triggerViews', 'triggers_by_worker', {reduce: true, group: true}, function (err, body) {
+                    if (!err) {
+                        var triggersByWorker = {};
+
+                        availabeWorkers.forEach(worker => {
+                            triggersByWorker[worker] = 0;
+                        });
+
+                        body.rows.forEach(row => {
+                            if (row.key in triggersByWorker) {
+                                triggersByWorker[row.key] = row.value;
+                            }
+                        });
+
+                        // find which worker has the least number of assigned triggers
+                        for (var worker in triggersByWorker) {
+                            if (triggersByWorker[worker] < triggersByWorker[workerID]) {
+                                workerID = worker;
+                            }
+                        }
+                        resolve(workerID);
+                    } else {
+                        reject(err);
+                    }
+                });
+            }
+            else {
+                resolve(workerID);
+            }
+        });
+    };
+
+    this.createTrigger = function(triggerID, newTrigger) {
+
+        return new Promise(function(resolve, reject) {
+
+            utilsDB.db.insert(newTrigger, triggerID, function (err) {
+                if (!err) {
+                    resolve();
+                }
+                else {
+                    reject(common.sendError(err.statusCode, 'error creating cloudant trigger.', err.message));
+                }
+            });
+        });
+    };
+
+    this.getTrigger = function(triggerID) {
+
+        return new Promise(function(resolve, reject) {
+
+            utilsDB.db.get(triggerID, function (err, existing) {
+                if (err) {
+                    var qName = triggerID.split(':');
+                    var name = '/' + qName[1] + '/' + qName[2];
+                    reject(common.sendError(err.statusCode, 'could not find trigger ' + name + ' in the database'));
+                } else {
+                    resolve(existing);
+                }
+            });
+        });
+    };
+
+    this.disableTrigger = function(triggerID, trigger, retryCount, crudMessage) {
+
+        if (retryCount === 0) {
+            //check if it is already disabled
+            if (trigger.status && trigger.status.active === false) {
+                return Promise.resolve(triggerID);
+            }
+
+            var message = `Automatically disabled trigger while ${crudMessage}`;
+            var status = {
+                'active': false,
+                'dateChanged': Date.now(),
+                'reason': {'kind': 'AUTO', 'statusCode': undefined, 'message': message}
+            };
+            trigger.status = status;
+        }
+
+        return new Promise(function(resolve, reject) {
+
+            utilsDB.db.insert(trigger, triggerID, function (err) {
+                if (err) {
+                    if (err.statusCode === 409 && retryCount < 5) {
+                        setTimeout(function () {
+                            utilsDB.disableTrigger(triggerID, trigger, (retryCount + 1))
+                            .then(id => {
+                                resolve(id);
+                            })
+                            .catch(err => {
+                                reject(err);
+                            });
+                        }, 1000);
+                    }
+                    else {
+                        reject(common.sendError(err.statusCode, 'there was an error while disabling the trigger in the database.', err.message));
+                    }
+                }
+                else {
+                    resolve(triggerID);
+                }
+            });
+        });
+
+    };
+
+    this.deleteTrigger = function(triggerID, retryCount) {
+
+        return new Promise(function(resolve, reject) {
+
+            utilsDB.db.get(triggerID, function (err, existing) {
+                if (!err) {
+                    utilsDB.db.destroy(existing._id, existing._rev, function (err) {
+                        if (err) {
+                            if (err.statusCode === 409 && retryCount < 5) {
+                                setTimeout(function () {
+                                    utilsDB.deleteTrigger(triggerID, (retryCount + 1))
+                                    .then(resolve)
+                                    .catch(err => {
+                                        reject(err);
+                                    });
+                                }, 1000);
+                            }
+                            else {
+                                reject(common.sendError(err.statusCode, 'there was an error while deleting the trigger from the database.', err.message));
+                            }
+                        }
+                        else {
+                            resolve();
+                        }
+                    });
+                }
+                else {
+                    var qName = triggerID.split(':');
+                    var name = '/' + qName[1] + '/' + qName[2];
+                    reject(common.sendError(err.statusCode, 'could not find trigger ' + name + ' in the database'));
+                }
+            });
+        });
+    };
+
+    this.updateTrigger = function(triggerID, trigger, params, retryCount) {
+
+        if (retryCount === 0) {
+            for (var key in params) {
+                trigger[key] = params[key];
+            }
+            var status = {
+                'active': true,
+                'dateChanged': Date.now()
+            };
+            trigger.status = status;
+        }
+
+        return new Promise(function(resolve, reject) {
+            utilsDB.db.insert(trigger, triggerID, function (err) {
+                if (err) {
+                    if (err.statusCode === 409 && retryCount < 5) {
+                        setTimeout(function () {
+                            utilsDB.updateTrigger(triggerID, trigger, params, (retryCount + 1))
+                            .then(id => {
+                                resolve(id);
+                            })
+                            .catch(err => {
+                                reject(err);
+                            });
+                        }, 1000);
+                    }
+                    else {
+                        reject(common.sendError(err.statusCode, 'there was an error while updating the trigger in the database.', err.message));
+                    }
+                }
+                else {
+                    resolve(triggerID);
+                }
+            });
+        });
+    };
+
+};
diff --git a/actions/event-actions/lib/common.js b/actions/event-actions/lib/common.js
new file mode 100644
index 0000000..e5ba69a
--- /dev/null
+++ b/actions/event-actions/lib/common.js
@@ -0,0 +1,137 @@
+const request = require('request');
+const openwhisk = require('openwhisk');
+const config = require('./config');
+
+function requestHelper(url, input, method) {
+
+    return new Promise(function(resolve, reject) {
+
+        var options = {
+            method : method,
+            url : url,
+            json: true,
+            rejectUnauthorized: false
+        };
+
+        if (method === 'get') {
+            options.qs = input;
+        } else {
+            options.body = input;
+        }
+
+        request(options, function(error, response, body) {
+
+            if (!error && response.statusCode === 200) {
+                resolve(body);
+            }
+            else {
+                if (response) {
+                    console.log('cloudant: Error invoking whisk action:', response.statusCode, body);
+                    reject(body);
+                }
+                else {
+                    console.log('cloudant: Error invoking whisk action:', error);
+                    reject(error);
+                }
+            }
+        });
+    });
+}
+
+function createWebParams(rawParams) {
+    var namespace = process.env.__OW_NAMESPACE;
+    var triggerName = ':' + namespace + ':' + parseQName(rawParams.triggerName, '/').name;
+
+    var webparams = Object.assign({}, rawParams);
+    delete webparams.lifecycleEvent;
+    delete webparams.bluemixServiceName;
+    delete webparams.apihost;
+
+    webparams.triggerName = triggerName;
+    webparams.authKey = process.env.__OW_API_KEY;
+    config.addAdditionalData(webparams);
+
+    return webparams;
+}
+
+function verifyTriggerAuth(triggerData, isDelete) {
+    var owConfig = config.getOpenWhiskConfig(triggerData);
+    var ow = openwhisk(owConfig);
+
+    return new Promise(function(resolve, reject) {
+        ow.triggers.get(triggerData.name)
+        .then(() => {
+            resolve();
+        })
+        .catch(err => {
+           if (err.statusCode) {
+               var statusCode = err.statusCode;
+               if (!(isDelete && statusCode === 404)) {
+                   reject(sendError(statusCode, 'Trigger authentication request failed.'));
+               }
+               else {
+                   resolve();
+               }
+           }
+           else {
+               reject(sendError(400, 'Trigger authentication request failed.', err.message));
+           }
+        });
+    });
+}
+
+function parseQName(qname, separator) {
+    var parsed = {};
+    var delimiter = separator || ':';
+    var defaultNamespace = '_';
+    if (qname && qname.charAt(0) === delimiter) {
+        var parts = qname.split(delimiter);
+        parsed.namespace = parts[1];
+        parsed.name = parts.length > 2 ? parts.slice(2).join(delimiter) : '';
+    } else {
+        parsed.namespace = defaultNamespace;
+        parsed.name = qname;
+    }
+    return parsed;
+}
+
+function sendError(statusCode, error, message) {
+    var params = {error: error};
+    if (message) {
+        params.message = message;
+    }
+
+    return {
+        statusCode: statusCode,
+        headers: { 'Content-Type': 'application/json' },
+        body: params
+    };
+}
+
+function constructObject(data) {
+    var jsonObject;
+    if (data) {
+        if (typeof data === 'string') {
+            try {
+                jsonObject = JSON.parse(data);
+            }
+            catch (e) {
+                console.log('error parsing ' + data);
+            }
+        }
+        if (typeof data === 'object') {
+            jsonObject = data;
+        }
+    }
+    return jsonObject;
+}
+
+
+module.exports = {
+    'requestHelper': requestHelper,
+    'createWebParams': createWebParams,
+    'verifyTriggerAuth': verifyTriggerAuth,
+    'parseQName': parseQName,
+    'sendError': sendError,
+    'constructObject': constructObject
+};
diff --git a/actions/event-actions/lib/config.js b/actions/event-actions/lib/config.js
new file mode 100644
index 0000000..550f42a
--- /dev/null
+++ b/actions/event-actions/lib/config.js
@@ -0,0 +1,13 @@
+function getOpenWhiskConfig(triggerData) {
+    return {ignore_certs: true, namespace: triggerData.namespace, api_key: triggerData.apikey};
+}
+
+function addAdditionalData(params) {
+    //insert code here to store additional trigger data in the database
+    //for example, params.additionalData = {dateCreated: Date.now()};
+}
+
+module.exports = {
+    'addAdditionalData': addAdditionalData,
+    'getOpenWhiskConfig': getOpenWhiskConfig
+};
diff --git a/installCatalog.sh b/installCatalog.sh
index cfa5521..d83fffe 100755
--- a/installCatalog.sh
+++ b/installCatalog.sh
@@ -12,9 +12,8 @@
 : ${OPENWHISK_HOME:?"OPENWHISK_HOME must be set and non-empty"}
 WSK_CLI="$OPENWHISK_HOME/bin/wsk"
 
-if [ $# -eq 0 ]
-then
-echo "Usage: ./installCatalog.sh <authkey> <edgehost> <dburl> <dbprefix> <apihost> <workers>"
+if [ $# -eq 0 ]; then
+    echo "Usage: ./installCatalog.sh <authkey> <edgehost> <dburl> <dbprefix> <apihost> <workers>"
 fi
 
 AUTH="$1"
@@ -23,6 +22,7 @@
 DB_NAME="${4}cloudanttrigger"
 APIHOST="$5"
 WORKERS="$6"
+ACTION_RUNTIME_VERSION=${ACTION_RUNTIME_VERSION:="nodejs:6"}
 
 # If the auth key file exists, read the key in the file. Otherwise, take the
 # first argument as the key itself.
@@ -48,7 +48,7 @@
 
 echo Installing Cloudant package.
 
-$WSK_CLI -i --apihost "$EDGEHOST" package update --auth "$AUTH"  --shared yes cloudant \
+$WSK_CLI -i --apihost "$EDGEHOST" package update --auth "$AUTH" --shared yes cloudant \
     -a description "Cloudant database service" \
     -a parameters '[ {"name":"bluemixServiceName", "required":false, "bindTime":true}, {"name":"username", "required":true, "bindTime":true, "description": "Your Cloudant username"}, {"name":"password", "required":true, "type":"password", "bindTime":true, "description": "Your Cloudant password"}, {"name":"host", "required":true, "bindTime":true, "description": "This is usually your username.cloudant.com"}, {"name":"dbname", "required":false, "description": "The name of your Cloudant database"}, {"name":"overwrite", "required":false, "type": "boolean"} ]' \
     -p bluemixServiceName 'cloudantNoSQLDB' \
@@ -58,127 +58,139 @@
     -p dbname '' \
     -p apihost "$APIHOST"
 
-if [ -n "$WORKERS" ];
-then
-    $WSK_CLI -i --apihost "$EDGEHOST" package update --auth "$AUTH" --shared no cloudantWeb \
-        -p DB_URL "$DB_URL" \
-        -p DB_NAME "$DB_NAME" \
-        -p apihost "$APIHOST" \
-        -p workers "$WORKERS"
-else
-    $WSK_CLI -i --apihost "$EDGEHOST" package update --auth "$AUTH" --shared no cloudantWeb \
-        -p DB_URL "$DB_URL" \
-        -p DB_NAME "$DB_NAME" \
-        -p apihost "$APIHOST"
+# make changesFeed.zip
+cd actions/event-actions
+
+if [ -e changesFeed.zip ]; then
+    rm -rf changesFeed.zip
 fi
 
-# Cloudant feed action
+cp -f changesFeed_package.json package.json
+zip -r changesFeed.zip lib package.json changes.js
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/changes "$PACKAGE_HOME/actions/changes.js" \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/changes "$PACKAGE_HOME/actions/event-actions/changesFeed.zip" \
     -t 90000 \
     -a feed true \
     -a description 'Database change feed' \
     -a parameters '[ {"name":"dbname", "required":true, "updatable":false}, {"name": "filter", "required":false, "updatable":true, "type": "string", "description": "The name of your Cloudant database filter"}, {"name": "query_params", "required":false, "updatable":true, "description": "JSON Object containing query parameters that are passed to the filter"} ]' \
     -a sampleInput '{ "dbname": "mydb", "filter": "mailbox/by_status", "query_params": {"status": "new"} }'
 
-# Cloudant web feed action
+COMMAND=" -i --apihost $EDGEHOST package update --auth $AUTH --shared no cloudantWeb \
+     -p DB_URL $DB_URL \
+     -p DB_NAME $DB_NAME \
+     -p apihost $APIHOST"
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudantWeb/changesWebAction "$PACKAGE_HOME/actions/changesWebAction.js" \
+if [ -n "$WORKERS" ]; then
+    COMMAND+=" -p workers $WORKERS"
+fi
+
+$WSK_CLI $COMMAND
+
+# make changesWebAction.zip
+cp -f changesWeb_package.json package.json
+
+if [ -e changesWebAction.zip ]; then
+    rm -rf changesWebAction.zip
+fi
+
+zip -r changesWebAction.zip lib package.json changesWebAction.js
+
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudantWeb/changesWebAction "$PACKAGE_HOME/actions/event-actions/changesWebAction.zip" \
     -a description 'Create/Delete a trigger in cloudant provider Database' \
     --web true
 
 # Cloudant account actions
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/create-database \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/create-database \
     "$PACKAGE_HOME/actions/account-actions/create-database.js" \
     -a description 'Create Cloudant database' \
     -a parameters '[ {"name":"dbname", "required":true} ]'
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/read-database \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/read-database \
     "$PACKAGE_HOME/actions/account-actions/read-database.js" \
     -a description 'Read Cloudant database' \
     -a parameters '[ {"name":"dbname", "required":true} ]'
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/delete-database \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/delete-database \
     "$PACKAGE_HOME/actions/account-actions/delete-database.js" \
     -a description 'Delete Cloudant database' \
     -a parameters '[ {"name":"dbname", "required":true} ]'
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/list-all-databases \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/list-all-databases \
     "$PACKAGE_HOME/actions/account-actions/list-all-databases.js" \
     -a description 'List all Cloudant databases'
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/read-updates-feed \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/read-updates-feed \
     "$PACKAGE_HOME/actions/account-actions/read-updates-feed.js" \
     -a description 'Read updates feed from Cloudant account (non-continuous)' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"params", "required":false} ]'
 
 # Cloudant database actions
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/create-document \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/create-document \
     "$PACKAGE_HOME/actions/database-actions/create-document.js" \
     -a description 'Create document in database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"doc", "required":true, "description": "The JSON document to insert"}, {"name":"params", "required":false} ]' \
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/read \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/read \
     "$PACKAGE_HOME/actions/database-actions/read-document.js" \
     -a description 'Read document from database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"id", "required":true, "description": "The Cloudant document id to fetch"}, {"name":"params", "required":false}]' \
     -p id ''
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/read-document \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/read-document \
     "$PACKAGE_HOME/actions/database-actions/read-document.js" \
     -a description 'Read document from database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true, "description": "The Cloudant document id to fetch"}, {"name":"params", "required":false}]' \
     -p docid ''
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/write \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/write \
     "$PACKAGE_HOME/actions/database-actions/write-document.js" \
     -a description 'Write document in database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"doc", "required":true} ]' \
     -p doc '{}'
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/update-document \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/update-document \
     "$PACKAGE_HOME/actions/database-actions/update-document.js" \
     -a description 'Update document in database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"doc", "required":true}, {"name":"params", "required":false} ]' \
     -p doc '{}'
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/delete-document \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/delete-document \
     "$PACKAGE_HOME/actions/database-actions/delete-document.js" \
     -a description 'Delete document from database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true, "description": "The Cloudant document id to delete"},  {"name":"docrev", "required":true, "description": "The document revision number"} ]' \
     -p docid '' \
     -p docrev ''
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/list-documents \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/list-documents \
     "$PACKAGE_HOME/actions/database-actions/list-documents.js" \
     -a description 'List all docs from database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"params", "required":false} ]'
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/list-design-documents \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/list-design-documents \
     "$PACKAGE_HOME/actions/database-actions/list-design-documents.js" \
     -a description 'List design documents from database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"includedocs", "required":false} ]' \
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/create-query-index \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/create-query-index \
     "$PACKAGE_HOME/actions/database-actions/create-query-index.js" \
     -a description 'Create a Cloudant Query index into database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"index", "required":true} ]' \
     -p index ''
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/list-query-indexes \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/list-query-indexes \
     "$PACKAGE_HOME/actions/database-actions/list-query-indexes.js" \
     -a description 'List Cloudant Query indexes from database' \
     -a parameters '[ {"name":"dbname", "required":true} ]' \
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/exec-query-find \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/exec-query-find \
     "$PACKAGE_HOME/actions/database-actions/exec-query-find.js" \
     -a description 'Execute query against Cloudant Query index' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"query", "required":true} ]' \
     -p query ''
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/exec-query-search \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/exec-query-search \
     "$PACKAGE_HOME/actions/database-actions/exec-query-search.js" \
     -a description 'Execute query against Cloudant search' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"indexname", "required":true}, {"name":"search", "required":true} ]' \
@@ -186,39 +198,39 @@
     -p indexname '' \
     -p search ''
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/exec-query-view \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/exec-query-view \
     "$PACKAGE_HOME/actions/database-actions/exec-query-view.js" \
     -a description 'Call view in design document from database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"viewname", "required":true}, {"name":"params", "required":false} ]' \
     -p docid '' \
     -p viewname ''
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/manage-bulk-documents \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/manage-bulk-documents \
     "$PACKAGE_HOME/actions/database-actions/manage-bulk-documents.js" \
     -a description 'Create, Update, and Delete documents in bulk' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"docs", "required":true}, {"name":"params", "required":false} ]' \
     -p docs '{}'
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/delete-view \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/delete-view \
     "$PACKAGE_HOME/actions/database-actions/delete-view.js" \
     -a description 'Delete view from design document' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"viewname", "required":true}, {"name":"params", "required":false} ]' \
     -p docid '' \
     -p viewname ''
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/delete-query-index \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/delete-query-index \
     "$PACKAGE_HOME/actions/database-actions/delete-query-index.js" \
     -a description 'Delete index from design document' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"indexname", "required":true}, {"name":"params", "required":false} ]' \
     -p docid '' \
     -p indexname ''
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/read-changes-feed \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/read-changes-feed \
     "$PACKAGE_HOME/actions/database-actions/read-changes-feed.js" \
     -a description 'Read Cloudant database changes feed (non-continuous)' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"params", "required":false} ]'
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/create-attachment \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/create-attachment \
     "$PACKAGE_HOME/actions/database-actions/create-update-attachment.js" \
     -a description 'Create document attachment in database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"docrev", "required":true}, {"name":"attachment", "required":true}, {"name":"attachmentname", "required":true}, {"name":"contenttype", "required":true}, {"name":"params", "required":false} ]' \
@@ -228,14 +240,14 @@
     -p attachmentname '' \
     -p contenttype ''
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/read-attachment \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/read-attachment \
     "$PACKAGE_HOME/actions/database-actions/read-attachment.js" \
     -a description 'Read document attachment from database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"attachmentname", "required":true}, {"name":"params", "required":false} ]' \
     -p docid '' \
     -p attachmentname ''
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/update-attachment \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/update-attachment \
     "$PACKAGE_HOME/actions/database-actions/create-update-attachment.js" \
     -a description 'Update document attachment in database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"docrev", "required":true}, {"name":"attachment", "required":true}, {"name":"attachmentname", "required":true}, {"name":"contenttype", "required":true}, {"name":"params", "required":false} ]' \
@@ -245,7 +257,7 @@
     -p attachmentname '' \
     -p contenttype ''
 
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" cloudant/delete-attachment \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" cloudant/delete-attachment \
     "$PACKAGE_HOME/actions/database-actions/delete-attachment.js" \
     -a description 'Delete document attachment from database' \
     -a parameters '[ {"name":"dbname", "required":true}, {"name":"docid", "required":true}, {"name":"docrev", "required":true}, {"name":"attachmentname", "required":true}, {"name":"params", "required":false} ]' \
diff --git a/provider/lib/authHandler.js b/provider/lib/authHandler.js
new file mode 100644
index 0000000..9e25242
--- /dev/null
+++ b/provider/lib/authHandler.js
@@ -0,0 +1,13 @@
+function handleAuth(triggerData) {
+
+    var auth = triggerData.apikey.split(':');
+    return Promise.resolve({
+        user: auth[0],
+        pass: auth[1]
+    });
+
+}
+
+module.exports = {
+    'handleAuth': handleAuth
+};
diff --git a/provider/lib/health.js b/provider/lib/health.js
index c2055a3..f329ba9 100644
--- a/provider/lib/health.js
+++ b/provider/lib/health.js
@@ -1,6 +1,5 @@
 var si = require('systeminformation');
 var v8 = require('v8');
-var request = require('request');
 var _ = require('lodash');
 var URL = require('url').URL;
 var constants = require('./constants.js');
@@ -47,8 +46,6 @@
     this.monitor = function(apikey, monitoringInterval) {
         var method = 'monitor';
 
-        var auth = apikey.split(':');
-
         if (triggerName) {
             monitorStatus = Object.assign({}, utils.monitorStatus);
             utils.monitorStatus = {};
@@ -63,8 +60,12 @@
             var existingCanaryID = canaryDocID;
 
             //delete the trigger
-            var uri = utils.uriHost + '/api/v1/namespaces/_/triggers/' + triggerName;
-            deleteTrigger(existingTriggerID, uri, auth, 0);
+            var triggerData = {
+                apikey: apikey,
+                uri: utils.uriHost + '/api/v1/namespaces/_/triggers/' + triggerName,
+                triggerID: existingTriggerID
+            };
+            deleteTrigger(triggerData, 0);
 
             //delete the canary doc
             deleteDocFromDB(existingCanaryID, 0);
@@ -81,7 +82,7 @@
 
         var triggerURL = utils.uriHost + '/api/v1/namespaces/_/triggers/' + triggerName;
         var triggerID = `:_:${triggerName}`;
-        createTrigger(triggerURL, auth)
+        createTrigger(triggerURL, apikey)
         .then(info => {
             logger.info(method, triggerID, info);
             var newTrigger = createCloudantTrigger(triggerID, apikey);
@@ -126,17 +127,13 @@
         return newTrigger;
     }
 
-    function createTrigger(triggerURL, auth) {
+    function createTrigger(triggerURL, apikey) {
         var method = 'createTrigger';
 
         return new Promise(function(resolve, reject) {
-            request({
+            utils.authRequest({apikey: apikey}, {
                 method: 'put',
                 uri: triggerURL,
-                auth: {
-                    user: auth[0],
-                    pass: auth[1]
-                },
                 json: true,
                 body: {}
             }, function (error, response) {
@@ -163,23 +160,20 @@
         });
     }
 
-    function deleteTrigger(triggerID, uri, auth, retryCount) {
+    function deleteTrigger(triggerData, retryCount) {
         var method = 'deleteTrigger';
 
-        request({
+        var triggerID = triggerData.triggerID;
+        utils.authRequest(triggerData, {
             method: 'delete',
-            uri: uri,
-            auth: {
-                user: auth[0],
-                pass: auth[1]
-            },
+            uri: triggerData.uri
         }, function (error, response) {
             logger.info(method, triggerID, 'http delete request, STATUS:', response ? response.statusCode : undefined);
             if (error || response.statusCode >= 400) {
                 if (!error && response.statusCode === 409 && retryCount < 5) {
                     logger.info(method, 'attempting to delete trigger again', triggerID, 'Retry Count:', (retryCount + 1));
                     setTimeout(function () {
-                        deleteTrigger(triggerID, uri, auth, (retryCount + 1));
+                        deleteTrigger(triggerData, (retryCount + 1));
                     }, 1000);
                 } else {
                     logger.error(method, triggerID, 'trigger delete request failed');
diff --git a/provider/lib/utils.js b/provider/lib/utils.js
index 808beb4..3e3ab61 100644
--- a/provider/lib/utils.js
+++ b/provider/lib/utils.js
@@ -1,6 +1,7 @@
 var request = require('request');
 var HttpStatus = require('http-status-codes');
 var constants = require('./constants.js');
+var authHandler = require('./authHandler');
 
 module.exports = function(logger, triggerDB, redisClient) {
 
@@ -26,41 +27,41 @@
     this.monitorStatus = {};
 
     // Add a trigger: listen for changes and dispatch.
-    this.createTrigger = function(dataTrigger) {
+    this.createTrigger = function(triggerData) {
         var method = 'createTrigger';
 
         // both couch and cloudant should have their URLs in the username:password@host format
-        var dbURL = `${dataTrigger.protocol}://${dataTrigger.user}:${dataTrigger.pass}@${dataTrigger.host}`;
+        var dbURL = `${triggerData.protocol}://${triggerData.user}:${triggerData.pass}@${triggerData.host}`;
 
         // add port if specified
-        if (dataTrigger.port) {
-            dbURL += ':' + dataTrigger.port;
+        if (triggerData.port) {
+            dbURL += ':' + triggerData.port;
         }
 
         try {
             var nanoConnection = require('cloudant-nano')(dbURL);
-            var triggeredDB = nanoConnection.use(dataTrigger.dbname);
+            var triggeredDB = nanoConnection.use(triggerData.dbname);
 
             // Listen for changes on this database.
-            var feed = triggeredDB.follow({since: dataTrigger.since, include_docs: false});
-            if (dataTrigger.filter) {
-                feed.filter = dataTrigger.filter;
+            var feed = triggeredDB.follow({since: triggerData.since, include_docs: false});
+            if (triggerData.filter) {
+                feed.filter = triggerData.filter;
             }
-            if (dataTrigger.query_params) {
-                feed.query_params = dataTrigger.query_params;
+            if (triggerData.query_params) {
+                feed.query_params = triggerData.query_params;
             }
 
-            dataTrigger.feed = feed;
-            self.triggers[dataTrigger.id] = dataTrigger;
+            triggerData.feed = feed;
+            self.triggers[triggerData.id] = triggerData;
 
             feed.on('change', function (change) {
-                var triggerHandle = self.triggers[dataTrigger.id];
+                var triggerHandle = self.triggers[triggerData.id];
                 if (triggerHandle && shouldFireTrigger(triggerHandle) && hasTriggersRemaining(triggerHandle)) {
-                    logger.info(method, 'Trigger', dataTrigger.id, 'got change from', dataTrigger.dbname);
+                    logger.info(method, 'Trigger', triggerData.id, 'got change from', triggerData.dbname);
                     try {
-                        fireTrigger(dataTrigger.id, change);
+                        fireTrigger(triggerData.id, change);
                     } catch (e) {
-                        logger.error(method, 'Exception occurred while firing trigger', dataTrigger.id, e);
+                        logger.error(method, 'Exception occurred while firing trigger', triggerData.id, e);
                     }
                 }
             });
@@ -69,21 +70,21 @@
 
             return new Promise(function(resolve, reject) {
                 feed.on('error', function (err) {
-                    logger.error(method,'Error occurred for trigger', dataTrigger.id, '(db ' + dataTrigger.dbname + '):', err);
+                    logger.error(method,'Error occurred for trigger', triggerData.id, '(db ' + triggerData.dbname + '):', err);
                     reject(err);
                 });
 
                 feed.on('confirm', function () {
-                    logger.info(method, 'Added cloudant data trigger', dataTrigger.id, 'listening for changes in database', dataTrigger.dbname);
-                    if (isMonitoringTrigger(dataTrigger.monitor, dataTrigger.id)) {
+                    logger.info(method, 'Added cloudant data trigger', triggerData.id, 'listening for changes in database', triggerData.dbname);
+                    if (isMonitoringTrigger(triggerData.monitor, triggerData.id)) {
                         self.monitorStatus.triggerStarted = "success";
                     }
-                    resolve(dataTrigger.id);
+                    resolve(triggerData.id);
                 });
             });
 
         } catch (err) {
-            logger.info(method, 'caught an exception for trigger', dataTrigger.id, err);
+            logger.info(method, 'caught an exception for trigger', triggerData.id, err);
             return Promise.reject(err);
         }
 
@@ -105,7 +106,8 @@
             maxTriggers: maxTriggers,
             triggersLeft: maxTriggers,
             filter: newTrigger.filter,
-            query_params: newTrigger.query_params
+            query_params: newTrigger.query_params,
+            additionalData: newTrigger.additionalData
         };
 
         return trigger;
@@ -181,27 +183,27 @@
     function fireTrigger(triggerIdentifier, change) {
         var method = 'fireTrigger';
 
-        var dataTrigger = self.triggers[triggerIdentifier];
-        var triggerObj = parseQName(dataTrigger.id);
+        var triggerData = self.triggers[triggerIdentifier];
+        var triggerObj = parseQName(triggerData.id);
 
         var form = change;
-        form.dbname = dataTrigger.dbname;
+        form.dbname = triggerData.dbname;
 
-        logger.info(method, 'firing trigger', dataTrigger.id, 'with db update');
+        logger.info(method, 'firing trigger', triggerData.id, 'with db update');
 
         var host = 'https://' + self.routerHost + ':' + 443;
         var uri = host + '/api/v1/namespaces/' + triggerObj.namespace + '/triggers/' + triggerObj.name;
-        var auth = dataTrigger.apikey.split(':');
+        var auth = triggerData.apikey.split(':');
 
-        postTrigger(dataTrigger, form, uri, auth, 0)
+        postTrigger(triggerData, form, uri, auth, 0)
         .then(triggerId => {
             logger.info(method, 'Trigger', triggerId, 'was successfully fired');
-            if (isMonitoringTrigger(dataTrigger.monitor, triggerId)) {
+            if (isMonitoringTrigger(triggerData.monitor, triggerId)) {
                 self.monitorStatus.triggerFired = "success";
             }
-            if (dataTrigger.triggersLeft === 0) {
-                if (dataTrigger.monitor) {
-                    deleteTrigger(triggerId, dataTrigger.monitor);
+            if (triggerData.triggersLeft === 0) {
+                if (triggerData.monitor) {
+                    deleteTrigger(triggerId, triggerData.monitor);
                 }
                 else {
                     disableTrigger(triggerId, undefined, 'Automatically disabled after reaching max triggers');
@@ -214,45 +216,41 @@
         });
     }
 
-    function postTrigger(dataTrigger, form, uri, auth, retryCount) {
+    function postTrigger(triggerData, form, uri, auth, retryCount) {
         var method = 'postTrigger';
 
         return new Promise(function(resolve, reject) {
 
             // only manage trigger fires if they are not infinite
-            if (dataTrigger.maxTriggers !== -1) {
-                dataTrigger.triggersLeft--;
+            if (triggerData.maxTriggers !== -1) {
+                triggerData.triggersLeft--;
             }
 
-            request({
+            self.authRequest(triggerData, {
                 method: 'post',
                 uri: uri,
-                auth: {
-                    user: auth[0],
-                    pass: auth[1]
-                },
                 json: form
             }, function(error, response) {
                 try {
-                    logger.info(method, dataTrigger.id, 'http post request, STATUS:', response ? response.statusCode : response);
+                    logger.info(method, triggerData.id, 'http post request, STATUS:', response ? response.statusCode : response);
                     if (error || response.statusCode >= 400) {
                         // only manage trigger fires if they are not infinite
-                        if (dataTrigger.maxTriggers !== -1) {
-                            dataTrigger.triggersLeft++;
+                        if (triggerData.maxTriggers !== -1) {
+                            triggerData.triggersLeft++;
                         }
-                        logger.error(method, 'there was an error invoking', dataTrigger.id, response ? response.statusCode : error);
+                        logger.error(method, 'there was an error invoking', triggerData.id, response ? response.statusCode : error);
                         if (!error && shouldDisableTrigger(response.statusCode)) {
                             //disable trigger
                             var message = 'Automatically disabled after receiving a ' + response.statusCode + ' status code when firing the trigger';
-                            disableTrigger(dataTrigger.id, response.statusCode, message);
-                            reject('Disabled trigger ' + dataTrigger.id + ' due to status code: ' + response.statusCode);
+                            disableTrigger(triggerData.id, response.statusCode, message);
+                            reject('Disabled trigger ' + triggerData.id + ' due to status code: ' + response.statusCode);
                         }
                         else {
                             if (retryCount < retryAttempts ) {
                                 var timeout = response && response.statusCode === 429 && retryCount === 0 ? 60000 : 1000 * Math.pow(retryCount + 1, 2);
-                                logger.info(method, 'attempting to fire trigger again', dataTrigger.id, 'Retry Count:', (retryCount + 1));
+                                logger.info(method, 'attempting to fire trigger again', triggerData.id, 'Retry Count:', (retryCount + 1));
                                 setTimeout(function () {
-                                    postTrigger(dataTrigger, form, uri, auth, (retryCount + 1))
+                                    postTrigger(triggerData, form, uri, auth, (retryCount + 1))
                                     .then(triggerId => {
                                         resolve(triggerId);
                                     })
@@ -261,12 +259,12 @@
                                     });
                                 }, timeout);
                             } else {
-                                reject('Unable to reach server to fire trigger ' + dataTrigger.id);
+                                reject('Unable to reach server to fire trigger ' + triggerData.id);
                             }
                         }
                     } else {
-                        logger.info(method, 'fired', dataTrigger.id, dataTrigger.triggersLeft, 'triggers left');
-                        resolve(dataTrigger.id);
+                        logger.info(method, 'fired', triggerData.id, triggerData.triggersLeft, 'triggers left');
+                        resolve(triggerData.id);
                     }
                 }
                 catch(err) {
@@ -294,16 +292,11 @@
                         var triggerObj = parseQName(triggerIdentifier);
                         var host = 'https://' + self.routerHost + ':' + 443;
                         var triggerURL = host + '/api/v1/namespaces/' + triggerObj.namespace + '/triggers/' + triggerObj.name;
-                        var auth = doc.apikey.split(':');
 
                         logger.info(method, 'Checking if trigger', triggerIdentifier, 'still exists');
-                        request({
+                        self.authRequest(doc, {
                             method: 'get',
-                            url: triggerURL,
-                            auth: {
-                                user: auth[0],
-                                pass: auth[1]
-                            }
+                            url: triggerURL
                         }, function (error, response) {
                             //disable trigger in database if trigger is dead
                             if (!error && shouldDisableTrigger(response.statusCode)) {
@@ -499,4 +492,18 @@
         }
     }
 
+    this.authRequest = function(triggerData, options, cb) {
+        var method = 'authRequest';
+
+        authHandler.handleAuth(triggerData)
+        .then(auth => {
+            options.auth = auth;
+            request(options, cb);
+        })
+        .catch(err => {
+            logger.error(method, err);
+            request(options, cb);
+        });
+    };
+
 };