Nice, i am eagerly awaiting. Now i use a version of API Heartbeat that also shows a heartbeat in the API log (every 30 seconds) and logs all (? i hope) on() events. Maybe that is something that can be consolidated - and made really nice as my coding is of the 'make it work get  a bigger hammer style.  i basically added three functions:   mylog = function(msg) {         let d = new Date();         let lines = new Error().stack.split("\n");         log(d.toUTCString() + " " + lines[2].trim() + " " + msg);     }, logTimestamp = function() {         mylog('beat');     },    
logOnEvent = function(event, obj, prev){    
        mylog(`${event} ${obj.id}`);
        for (var p in prev) {        
            if (p !== 'bio' && p !== 'notes' && p !== '_defaulttoken' & p !== 'gmnotes') {            
                if( prev[p] !== obj.get(p)) {
                    mylog(`${event} ${obj.id} ${p} : ${prev[p]} => ${obj.get(p)}`);
                }
            }
        }        
    },
  and then added in startStopBeat()   setInterval(logTimestamp,30000);  and finally updated registerEventHandlers() to            registerEventHandlers = function() {         on('change:campaign:playerpageid', (obj,prev) => { logOnEvent("change:campaign:playerpageid",obj,null); });         on('change:campaign:turnorder', (obj,prev) => { logOnEvent("change:campaign:turnorder",obj,null); }); // prev generates too much logging         on('change:campaign:initiativepage', (obj,prev) => { logOnEvent("change:campaign:initiativepage",obj,prev); });         on("change:player", (obj,prev) => { logOnEvent("change:player",obj,prev); });         on('add:page', (obj) => { logOnEvent("add:page",obj.id,null); });         on("change:page", (obj,prev) => { logOnEvent("change:page",obj,prev); });         on('destroy:page', (obj) => { logOnEvent("destroy:page",obj.id,null); });                 on('add:path', (obj) => { logOnEvent("add:path",obj.id,null); });         on("change:path", (obj,prev) => { logOnEvent("change:path",obj,prev); });         on('destroy:path', (obj) => { logOnEvent("destroy:path",obj.id,null); });                 on('add:text', (obj) => { logOnEvent("add:text",obj.id,null); });         on("change:text", (obj,prev) => { logOnEvent("change:text",obj,prev); });         on('destroy:text', (obj) => { logOnEvent("destroy:text",obj.id,null); });                         on('add:graphic', (obj) => { logOnEvent("add:graphic",obj.id,null); });         on("change:graphic", (obj,prev) => { logOnEvent("change:graphic",obj,prev); });         on('destroy:graphic', (obj) => { logOnEvent("destroy:graphic",obj.id,null); });                 on('remove:graphic', (obj) => { logOnEvent("destroy:graphic",obj.id,null); });                 on('add:token', (obj) => { logOnEvent("add:token",obj.id,null); });         on("change:token", (obj,prev) => { logOnEvent("change:token",obj,prev); });         on('destroy:token', (obj) => { logOnEvent("destroy:token",obj.id,null); });                 on('add:card', (obj) => { logOnEvent("add:card",obj.id,null); });         on("change:card", (obj,prev) => { logOnEvent("change:card",obj,prev); });         on('destroy:card', (obj) => { logOnEvent("destroy:card",obj.id,null); });                 on('add:deck', (obj) => { logOnEvent("add:deck",obj.id,null); });         on("change:deck", (obj,prev) => { logOnEvent("change:deck",obj,prev); });         on('destroy:deck', (obj) => { logOnEvent("destroy:deck",obj.id,null); });                 on('add:character', (obj) => { logOnEvent("add:character",obj.id,null); });         on("change:character", (obj,prev) => { logOnEvent("change:character",obj,prev); });         on('destroy:character', (obj) => { logOnEvent("destroy:character",obj.id,null); });                 on('add:handout', (obj) => { logOnEvent("add:handout",obj.id,null); });         on("change:handout", (obj,prev) => { logOnEvent("change:handout",obj,prev); });         on('destroy:handout', (obj) => { logOnEvent("destroy:handout",obj.id,null); });                         on('add:attribute', (obj) => { logOnEvent("add:attribute",obj.id,null); });         on("change:attribute", (obj,prev) => { logOnEvent("change:attribute",obj,prev); });         on('destroy:attribute', (obj) => { logOnEvent("destroy:attribute",obj.id,null); });                         on('chat:message', handleInput);         on('change:player:_online', startStopBeat);     };  Maybe this should be two scripts. Nah.... Roll20 rolls it into one large script anyways.