docs: function Webhook(config) {
  	// Inject defaults if no configuration specified
  	if (!config) {
  		debug("webhook instantiated with default configuration");
  		config = {
  			// [WORKAROUND] in some container situation (ie, Cisco Shipped), we need to use an OVERRIDE_PORT to force our bot to start and listen to the port defined in the Dockerfile (ie, EXPOSE), 
  			// and not the PORT dynamically assigned by the host or scheduler.
  			port 			: process.env.OVERRIDE_PORT || process.env.PORT,
  			path			: process.env.WEBHOOK_PATH,
  			// [COMPAT] SPARK_TOKEN deprecated but still supported
  			token			: process.env.ACCESS_TOKEN || process.env.SPARK_TOKEN,
  			secret			: process.env.WEBHOOK_SECRET,
  			commandPrefix 	: process.env.COMMAND_PREFIX || "/"
  		};
  	} 
  
  	// Robustify: it is usually safer not to read ourselves, especially if no commandPrefix is specified
  	if (!config.ignoreSelf && !config.commandPrefix) {
  		debug("WARNING: configuration does not prevent for reading from yourself => possible infinite loop, continuing...");
  	}
  	
  	// Abort if mandatory config parameters are not present
  	if (!config.port) {
  		fine("no port specified, applying default");
  		config.port = 8080;
  	}
  	if (!config.path) {
  		fine("no path specified, applying default");
  		config.path = "/";
  	}
  
  	// If an access token is specified, create a command interpreter
  	if (!config.token) {
  		debug("no access token specified, will not fetch message contents and room titles, nor interpret commands");
  	}
  	this.token = config.token;
  
  	// Apply secret if specified
  	if (config.secret) {
  		this.secret = config.secret;
  		// Defaults to false when a secret is specified
  		if (!config.softSecretCheck) {
  			this.softSecretCheck = false;
  		}
  	}
  	if (config.softSecretCheck) {
  		this.softSecretCheck = config.softSecretCheck;
  	}
  
  
  	// Webhook listeners
  	this.listeners = {};
  	var self = this;
  	function fire(trigger) {
  		// Retreive listener for incoming event
  		var entry = trigger.resource + "/" + trigger.event;
  		var listener = self.listeners[entry];
  		if (!listener) {
  			debug("no listener found for resource/event: " + entry);
  			return;
  		}
  
  		// Invoke listener
  		debug("calling listener for resource/event: " + entry + ", with data context: " + trigger.data.id);
  		listener(trigger);		
  	}
  
  	// Initialize command processors
  	this.interpreter = new CommandInterpreter(config);
  	this.router = new CommandRouter(this);
  
  	// Webhook API routes
  	started = Date.now();
  	app.route(config.path)
  		.get(function (req, res) {
  			debug("healtch check hitted");
  			var package = require("../package.json");
  			res.json({
  				message			: "Congrats, your bot is up and running",
  				since			: new Date(started).toISOString(),
  				framework		: package.name + ", " + package.version,
  				tip				: "Don't forget to create WebHooks to start receiving events from Webex: https://developer.webex.com/endpoint-webhooks-post.html",
  				webhook			: {
  					secret			: (self.secret != null),
  					softSecretCheck	: self.softSecretCheck,
  					listeners		: Object.keys(self.listeners),
  				},
  				token			: (self.token != null),
  				account			: {
  					type		: self.interpreter.accountType,
  					nickName	: self.interpreter.nickName,
  					person		: self.interpreter.person
  				},
  				interpreter		: {
  					prefix		: self.interpreter.prefix,
  					trimMention	: self.interpreter.trimMention,
  					ignoreSelf  : self.interpreter.ignoreSelf
  				},
  				commands        : Object.keys(self.router.commands)
  			});
  		})
  		.post(function (req, res) {
  			debug("webhook invoked");
  			
  			// analyse incoming payload, should conform to Webex Teams webhook specifications
          	if (!req.body || !Utils.checkWebhookEvent(req.body)) {
  				debug("unexpected payload POSTed, aborting...");
  				res.status(400).json({message: "Bad payload for Webhook",
  										details: "either the bot is misconfigured or Webex is running a new API version"});
  				return;
  			}
  
  			// event is ready to be processed, let's send a response to Webex without waiting any longer
  			res.status(200).json({message: "message received and being processed by webhook"});
  
  			// process HMAC-SHA1 signature if a secret has been specified
  			// [NOTE@ for security reasons, we check the secret AFTER responding to Webex
  			if (self.secret) {
  				if (!Utils.checkSignature(self.secret, req)) {
  					if (!self.softSecretCheck) {
  						debug("HMAC-SHA1 signature does not match secret, aborting payload processing");
  						return;
  					}
  					else  {
  						fine("HMAC-SHA1 signature does not match secret, continuing....");
  					}
  				}
  				fine("signature check ok, continuing...")
  			}
  
  			// process incoming resource/event, see https://developer.webex.com/webhooks-explained.html
  			fire(req.body);	
  		});
  	
  	// Start bot
  	app.listen(config.port, function () {
  		debug("bot started on port: " + config.port);
  	});
  }