14. Webhooks¶
Errbot has a small integrated webserver that is capable of hooking up endpoints to methods inside your plugins.
You must configure the Webserver plugin before this functionality can be used. You can get the configuration template using !plugin config Webserver, from where it’s just a simple matter of plugging in the desired settings.
Note
There is a !generate certificate command to generate a self-signed certificate in case you want to enable SSL connections and do not have a certificate.
Warning
It is not recommended to expose Errbot’s webserver directly to the network. Instead, we recommend placing it behind a webserver such as nginx or Apache.
14.1. Simple webhooks¶
All you need to do for a plugin of yours to listen to a specific URI
is to apply the webhook()
decorator to your method.
Whatever it returns will be returned in response to the request:
from errbot import BotPlugin, webhook
class PluginExample(BotPlugin):
@webhook
def test(self, request):
self.log.debug(repr(request))
return "OK"
This will listen for POST requests on http://yourserver.tld:yourport/test/, and return “OK” as the response body.
Note
If you return None, an empty 200 response will be sent.
You can also set a custom URI pattern by providing the uri_rule parameter:
from errbot import BotPlugin, webhook
class PluginExample(BotPlugin):
@webhook('/example/<name>/<action>/')
def test(self, request, name, action):
return "User %s is performing %s" % (name, action)
Refer to the documentation on Flask’s route for details on the supported syntax (Errbot uses Flask internally).
14.2. Handling JSON request¶
If an incoming request has the MIME media type set to application/json the request will automatically be decoded as JSON. You will receive the result of calling json.loads() on request automatically so that you won’t have to do this yourself.
14.3. Handling form-encoded requests¶
Form-encoded requests (those with an application/x-www-form-urlencoded mimetype) are very simple to handle as well, you just need to specify the form_param parameter.
A good example for this is the GitHub format which posts a form with a payload parameter:
from errbot import BotPlugin, webhook
class Github(BotPlugin):
@webhook('/github/', form_param = 'payload')
def notification(self, payload):
for room in self.bot_config.CHATROOM_PRESENCE:
self.send(
self.build_identifier(room),
'Commit on %s!' % payload['repository']['name'],
)
14.4. The raw request¶
The above webhooks are convenient for simple tasks, but sometimes
you might wish to have more power and have access to the actual
request itself. By setting the raw parameter of the
webhook()
decorator to True, you will
be able to get the
flask.Request
which contains all the details about the actual request:
from errbot import BotPlugin, webhook
class PluginExample(BotPlugin):
@webhook(raw=True)
def test(self, request):
user_agent = request.headers.get("user-agent", "Unknown")
return f"Your user-agent is {user_agent}"
14.5. Returning custom headers and status codes¶
Adjusting the response headers, setting cookies or returning a different status code can all be done by manipulating the flask response object. The Flask docs on the response object explain this in more detail. Here’s an example of setting a custom header:
from errbot import BotPlugin, webhook
from flask import after_this_request
class PluginExample(BotPlugin):
@webhook
def example(self, incoming_request):
@after_this_request
def add_header(response):
response.headers['X-Powered-By'] = 'Errbot'
return "OK"
Flask also has various helpers such as the abort() method. Using this method we could, for example, return a 403 forbidden response like so:
from errbot import BotPlugin, webhook
from flask import abort
class PluginExample(BotPlugin):
@webhook
def example(self, incoming_request):
abort(403, "Forbidden")
14.6. Testing a webhook through chat¶
You can use the !webhook command to test webhooks without making an actual HTTP request, using the following format:
!webhook test /[endpoint] [post_content]
For example:
!webhook test /test
!webhook test /github payload=%7B%22pusher%22%3A%7B%22name%22%3A%22gbin%22%2C%22email%22%3A%22gbin%40gootz.net%22%7D%2C%22repository%22%3A%7B%22name%22%3A%22test%22%2C%22created_at%22%3A%222012-08-12T16%3A09%3A43-07%3A00%22%2C%22has_wiki%22%3Atrue%2C%22size%22%3A128%2C%22private%22%3Afalse%2C%22watchers%22%3A0%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fgbin%2Ftest%22%2C%22fork%22%3Afalse%2C%22pushed_at%22%3A%222012-08-12T16%3A26%3A35-07%3A00%22%2C%22has_downloads%22%3Atrue%2C%22open_issues%22%3A0%2C%22has_issues%22%3Atrue%2C%22stargazers%22%3A0%2C%22forks%22%3A0%2C%22description%22%3A%22ignore%20this%2C%20this%20is%20for%20testing%20the%20new%20err%20github%20integration%22%2C%22owner%22%3A%7B%22name%22%3A%22gbin%22%2C%22email%22%3A%22gbin%40gootz.net%22%7D%7D%2C%22forced%22%3Afalse%2C%22after%22%3A%22b3cd9e66e52e4783c1a0b98fbaaad6258669275f%22%2C%22head_commit%22%3A%7B%22added%22%3A%5B%5D%2C%22modified%22%3A%5B%22README.md%22%5D%2C%22timestamp%22%3A%222012-08-12T16%3A24%3A25-07%3A00%22%2C%22removed%22%3A%5B%5D%2C%22author%22%3A%7B%22name%22%3A%22Guillaume%20BINET%22%2C%22username%22%3A%22gbin%22%2C%22email%22%3A%22gbin%40gootz.net%22%7D%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fgbin%2Ftest%2Fcommit%2Fb3cd9e66e52e4783c1a0b98fbaaad6258669275f%22%2C%22id%22%3A%22b3cd9e66e52e4783c1a0b98fbaaad6258669275f%22%2C%22distinct%22%3Atrue%2C%22message%22%3A%22voila%22%2C%22committer%22%3A%7B%22name%22%3A%22Guillaume%20BINET%22%2C%22username%22%3A%22gbin%22%2C%22email%22%3A%22gbin%40gootz.net%22%7D%7D%2C%22deleted%22%3Afalse%2C%22commits%22%3A%5B%7B%22added%22%3A%5B%5D%2C%22modified%22%3A%5B%22README.md%22%5D%2C%22timestamp%22%3A%222012-08-12T16%3A24%3A25-07%3A00%22%2C%22removed%22%3A%5B%5D%2C%22author%22%3A%7B%22name%22%3A%22Guillaume%20BINET%22%2C%22username%22%3A%22gbin%22%2C%22email%22%3A%22gbin%40gootz.net%22%7D%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fgbin%2Ftest%2Fcommit%2Fb3cd9e66e52e4783c1a0b98fbaaad6258669275f%22%2C%22id%22%3A%22b3cd9e66e52e4783c1a0b98fbaaad6258669275f%22%2C%22distinct%22%3Atrue%2C%22message%22%3A%22voila%22%2C%22committer%22%3A%7B%22name%22%3A%22Guillaume%20BINET%22%2C%22username%22%3A%22gbin%22%2C%22email%22%3A%22gbin%40gootz.net%22%7D%7D%5D%2C%22ref%22%3A%22refs%2Fheads%2Fmaster%22%2C%22before%22%3A%2229b1f5e59b7799073b6d792ce76076c200987265%22%2C%22compare%22%3A%22https%3A%2F%2Fgithub.com%2Fgbin%2Ftest%2Fcompare%2F29b1f5e59b77...b3cd9e66e52e%22%2C%22created%22%3Afalse%7D
Note
You can get a list of all the endpoints with the !webstatus command.