diff --git a/README.md b/README.md index 2c9cf7c719..a861167573 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ - [Websocket API](#websocket-api) * [Events](#events) * [Actions](#actions) +- [Web hooks](#web-hooks) - [Entities](#entities) - [Core Installation](#core-installation) * [System package manager installation](#system-package-manager-installation) @@ -685,6 +686,41 @@ responses asynchronously on the same channel: -x '{"type": "requests", "action": "procedure.foo.bar"}' ``` +## Web hooks + +You can use Platypush to expose your custom routines as dynamic Web hooks that +can be called by any client. + +All you need is to register a listener for a +[`WebhookEvent`](https://docs.platypush.tech/platypush/events/http.hook.html#platypush.message.event.http.hook.WebhookEvent) + +```python +from platypush import run, when +from platypush.events.http.hook import WebhookEvent + +hook_token = "abcdefabcdef" + + +# Expose the hook under the /hook/at_home endpoint +@when(WebhookEvent, hook="at_home") +def at_home_webhook(event: WebhookEvent): + # Unlike the calls to /execute, custom web hooks are unauthenticated. + # If you want authentication, you'll need to implement your custom logic by + # parsing the event headers + if event.headers.get("X-Token") != hook_token: + # Tuple with + event.send_response(("Unauthorized", 401)) + return + + run('procedure.at_home') +``` + +Then you can invoke your custom logic over HTTP: + +```bash +❯ curl -H 'X-Token: abcdefabcdef' 'http://localhost:8008/hook/at_home' +``` + ## Entities Entities are another building block of Platypush. Many integrations will store diff --git a/platypush/message/event/http/hook.py b/platypush/message/event/http/hook.py index 134bcfe0bf..d5633877b7 100644 --- a/platypush/message/event/http/hook.py +++ b/platypush/message/event/http/hook.py @@ -57,20 +57,16 @@ class WebhookEvent(Event): ) def send_response(self, response): - output = response.output + output = getattr(response, 'output', response) if isinstance(output, tuple): - # A 3-sized tuple where the second element is an int and the third - # is a dict represents an HTTP response in the format `(data, - # http_code headers)`. - if ( - len(output) == 3 - and isinstance(output[1], int) - and isinstance(output[2], dict) - ): + # A 3-sized tuple where the first element is the response, the + # second element is the HTTP code and the third is a dict with the + # response headers + if len(output) in (2, 3) and isinstance(output[1], int): output = { '___data___': output[0], '___code___': output[1], - '___headers___': output[2], + '___headers___': output[2] if len(output) > 2 else {}, } else: # Normalize tuples to lists before serialization