{
"info": {
"author": "Chad Lung",
"author_email": "chad.lung@gmail.com",
"bugtrack_url": null,
"classifiers": [
"Development Status :: 4 - Beta",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: MacOS :: MacOS X",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3.6"
],
"description": "PyWebhooks\n==========\n\n*A simple webhooks service*\n\n.. image:: https://travis-ci.org/chadlung/pywebhooks.svg?branch=master\n :target: https://travis-ci.org/chadlung/pywebhooks\n.. image:: https://coveralls.io/repos/chadlung/pywebhooks/badge.svg?branch=master&service=github \n :target: https://coveralls.io/github/chadlung/pywebhooks?branch=master\n.. image:: https://badge.fury.io/py/pywebhooks.svg\n :target: https://badge.fury.io/py/pywebhooks\n\n**Note:** PyWebhooks is ideally deployed on an internal private cloud/network where you\nknow and trust the end users and services using it. It should not be considered\nsecure enough (currently) to be a publicly deployed service.\n\nDon't like something? Need a feature? Please submit a pull request complete with\ntests and an update to the readme if required.\n\nIn order to run PyWebhooks you'll need to have `RethinkDB `__\nand `Redis `__ installed on a server or server(s). RethinkDB is\nused to store the account, webooks, etc. data. Redis is used by\n`Celery `__ to handle the calls to the\nwebhook endpoints.\n\n**Note:** PyWebhooks has been tested on Ubuntu 16.04 and OS X.\nPyWebhooks has been tested with Python 3.5.x and 3.6.x. Prior Python 3.x versions have not\nbeen tested and Python 2.x support is not planned.\n\nWhy PyWebhooks?\n^^^^^^^^^^^^^^^\n\nI looked all over for a project that did something similar to this. You can find\nplenty of code to listen for incoming webhooks as well as some code for sending webhooks.\nHowever, I couldn't find anything that wrapped it into a complete service where you could\nrun a server to allow for adding new accounts, letting those users create their\nown webhooks and then allow others to listen (subscribe) to those webhooks.\n\nUpdate - Feb. 9, 2019\n^^^^^^^^^^^^^^^^^^^^^\n\n- Vagrant support is dropped. The feedback I've received is only based on Docker support.\n- I'm planning to swap out the RethinkDB backend with Postgres/MySQL.\n- Also planned is no more static webhook messages - you could have messages sent with custom values.\n- Potentially removing Flask and replacing with Falcon.\n- More features and updates planned but too early to post them here. Its possible these new features\n and changes will just end up in an entirely new repository.\n\nQuickstart - Docker-Compose\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nMake sure you are running Docker version ``1.10+`` and Docker Compose ``1.6+`` or newer. From a command line run the following from the project's ``docker`` folder:\n\n::\n\n $ cd docker\n $ docker-compose up\n\nIf you can don't run that in daemon mode as you can more easily capture the admin ``secret_key`` and ``api_key`` from the console output.\nIt will look similar to this:\n\n::\n\n pywebhooks-server | Adding admin account\n pywebhooks-server | {'secret_key': 'd620fb92a70b7e5c127de74fcd717aa803f7e300', 'api_key': '7e8d21dda1c5738a30882e4520fbbfac55eebe3f'}\n\nMake sure to record those keys.\n\nNon-Quickstart\n^^^^^^^^^^^^^^\n\nIf you did't use the quick start mentioned above:\n\nOnce you have Redis and RethinkDB setup and running you can initialize the database and\nadmin accounts by running the following:\n\n::\n\n $ python app.py --initdb\n\n**Response:**\n\n::\n\n Dropping database...\n Creating database...\n Adding admin account\n {'secret_key': 'a6d8ff11a7cdb51130ea184b7228e179f3fd3a4c', 'api_key': 'ba86c64c24f361ddbcfe27be187d8d3002c9f43c'}\n Complete\n\nMake note of the admin ``api_key`` as it will be stored as a hash.\n\nWhen you create a new user account there are a few things to consider. First,\nyou need to have an endpoint setup where the account creation process can verify\nagainst. The endpoint can be whatever you want, a simple example would be a\nservice listening on: ``http://127.0.0.1:9090/account/endpoint``\n\nWhen you send the command to create the account if all goes well the PyWebhooks\nserver will hit the endpoint you specified with a challenge you need to echo back.\nThis helps ensure that you are actually setting up an endpoint that you control.\n\nThe PyWebhooks server will hit the endpoint you specified like this:\n``/account/endpoint?echo=2cac9beaa2f3b3aa72cc86faefb7575ba9c3c4b8``\n\nIt is your server's job to take that echo value and return it. In Python (using Flask)\nthis would look like:\n\n::\n\n @app.route('/account/endpoint', methods=['GET'])\n def echo():\n return make_response(jsonify({'echo': request.args.get('echo')}), client.OK)\n\n**Note:** PyWebhooks doesn't require your service be written in Python, any\nlanguage will work as long as it returns what is expected (in this case the echo value).\n\nIn Ruby 2.2.x using Sinatra a minimal endpoint server (handles Webhook POST traffic\nand GET echo requests) might look like this:\n\n::\n\n require 'rubygems'\n require 'openssl'\n require 'sinatra'\n require 'json'\n\n\n SHARED_SECRET = 'c27e823b0a500a537990dcccfc50334fe814fbd2'\n\n # Handle echo requests\n get '/account/endpoint' do\n content_type :json\n echo_value = params['echo']\n puts 'echo value:'\n puts(echo_value)\n\n status 200\n { :echo => echo_value }.to_json\n end\n\n # Handle the incoming webhook events\n post '/account/endpoint' do\n request.body.rewind\n data = request.body.read\n HMAC_DIGEST = OpenSSL::Digest.new('sha1')\n signature = OpenSSL::HMAC.hexdigest(HMAC_DIGEST, SHARED_SECRET, data)\n incoming_signature = env['HTTP_PYWEBHOOKS_SIGNATURE']\n\n puts 'hmac verification results:'\n puts Rack::Utils.secure_compare(signature, incoming_signature)\n\n incoming_event = env['HTTP_EVENT']\n puts 'incoming event is:'\n puts incoming_event\n puts 'incoming json is:'\n puts data\n\n status 200\n '{}'\n end\n\n\n**Note:** Pardon my Ruby, I'm rusty with it.\n\nA full Python endpoint example server code (for testing) can be a simple as:\n\n::\n\n import hashlib\n import hmac\n from http import client\n import json\n\n from flask import Flask\n from flask import request, make_response, jsonify\n\n\n app = Flask(__name__)\n\n # Adjust this as needed\n SECRET_KEY = 'c27e823b0a500a537990dcccfc50334fe814fbd2'\n\n\n def verify_hmac_hash(incoming_json, secret_key, incoming_signature):\n signature = hmac.new(\n str(secret_key).encode('utf-8'),\n str(incoming_json).encode('utf-8'),\n digestmod=hashlib.sha1\n ).hexdigest()\n\n return hmac.compare_digest(signature, incoming_signature)\n\n\n def create_response(req):\n if request.args.get('echo'):\n return make_response(jsonify({'echo': req.args.get('echo')}), client.OK)\n if request.args.get('api_key'):\n print('New api_key: {0}'.format(req.args.get('api_key')))\n return make_response(jsonify({}), client.OK)\n if request.args.get('secret_key'):\n print('New secret_key: {0}'.format(req.args.get('secret_key')))\n return make_response(jsonify({}), client.OK)\n\n\n def webhook_listener(request):\n print(request.headers)\n print(request.data)\n print(json.dumps(request.json))\n\n is_signature_valid = verify_hmac_hash(\n json.dumps(request.json),\n SECRET_KEY,\n request.headers['pywebhooks-signature']\n )\n\n print('Is Signature Valid?: {0}'.format(is_signature_valid))\n\n return make_response(jsonify({}), client.OK)\n\n\n @app.route('/account/endpoint', methods=['GET'])\n def echo():\n return create_response(request)\n\n\n @app.route('/account/alternate/endpoint', methods=['GET'])\n def echo_alternate():\n return create_response(request)\n\n\n @app.route('/account/alternate/endpoint', methods=['POST'])\n def account_alternate_listener():\n return webhook_listener(request)\n\n\n @app.route('/account/endpoint', methods=['POST'])\n def account_listener():\n return webhook_listener(request)\n\n\n if __name__ == '__main__':\n app.run(debug=True, port=9090, host='0.0.0.0')\n\n\nYou can save that code off into it's own project if you want just make sure to\ninstall Flask.\n\nNext, start one or more celery workers from the project root:\n\n::\n\n $ celery -A pywebhooks.tasks.webhook_notification worker --loglevel=info\n\nStart the main project in development mode:\n\n::\n\n $ python app.py\n\nWith your endpoint service and Celery worker running you can now perform\nthe following calls.\n\nAccount Actions\n^^^^^^^^^^^^^^^\n\n**Creating an account:**\n\nThe examples below use human readable user names. The reality is you should use\na complex username to avoid any potential possibility of someone abusing the\n``api_key`` reset as you only need a ``username`` to trigger a reset which could\nallow for a denial of service on your endpoint. A complex username not shared\nsuch as ``cRee82jfkjf09ij23`` is better than ``johndoe``. One potential fix\nI will look at is limiting how many ``api_key`` resets can be done in a given\nperiod (rate limiting). Also, the term \"username\" applies to the endpoint possibly\nbeing a service which is most likely the case so your username may actually be\nsomething like \"myservice-listener-001\" (as an example).\n\nIf ``127.0.0.1`` is not working below try ``localhost`` or lookup the IP Docker is using.\nMake sure to set that IP address in the ``endpoint`` below.\n\n**Note:** Make sure you are running an endpoint since creating an account will verfiy\nthe endpoint. You can use the example code above.\n\n::\n\n curl -v -X POST \"http://127.0.0.1:8081/v1/account\" -d '{\"endpoint\": \"http://127.0.0.1:9090/account/endpoint\", \"username\": \"sarahfranks\"}' -H \"content-type: application/json\"\n\n**Response:**\n\n**HTTP/1.0 201 CREATED**\n\n::\n\n {\n \"api_key\": \"be23d9ccb29082c489ba629077553ba1d8314005\",\n \"endpoint\": \"http://127.0.0.1:9090/account/endpoint\",\n \"epoch\": 1441164550.515677,\n \"id\": \"45712a61-a1b3-41a4-aa89-9593b909ae3d\",\n \"is_admin\": false,\n \"failed_count\": 0,\n \"secret_key\": \"5a4a1cf4895441a1dfaa504c471510be819198e7\",\n \"username\": \"sarahfranks\"\n }\n\nMake note of the ``id``, ``secret_key`` and ``api_key`` (because the ``api_key`` will be\nstored hashed).\n\nThe ``secret_key`` will be used to validate the data coming into your endpoint\nis indeed from the PyWebhooks server and not something/someone else. If you are\nfollowing along on a local dev machine make sure to stop your example endpoint server now\nand paste in the new ``secret_key`` value before running the next API call below. Now you \ncan re-start the example endpoint server.\n\nThe ``api_key`` will be used for any communication with the PyWebhooks server that\nisn't a publicly accessible call.\n\nThe ``id`` will be the account id.\n\nThe ``failed_count`` field tracks how many times an attempt (webhook POST) has\nfailed to contact the specified endpoint. ``MAX_FAILED_COUNT`` is a config value\nthat can be set (default is 250). If the ``failed_count`` exceeds the\n``MAX_FAILED_COUNT`` value then no more webhook posts will occur for the user\nuntil this is reset. A successful endpoint contact will automatically reset\nthis value to 0 if ``MAX_FAILED_COUNT`` has not been exceeded. This helps\nprevent an endpoint that is no longer responsive or moved (and not updated)\nfrom continuing to utilize system resources. In addition, updating the endpoint\nfor a account will also reset the ``failed_count``.\n\nRetries on webhook endpoints are done three times before giving up. The\n``DEFAULT_RETRY`` config value (defaults to 2 minutes) and ``DEFAULT_FINAL_RETRY``\nconfig value (defaults to 1 hour) can be adjusted for the three retries. Each\nfailed attempt to contact the endpoint results in an increment in the ``failed_count``\nfield of the user's account. If an endpoint is unreachable through the initial\nattempt to contact and the three retires then the ``failed_count`` value will\nbe four.\n\n**Get a single account record:**\n\nYou can only look-up your own account record.\n\n::\n\n curl -v -X GET \"http://127.0.0.1:8081/v1/account/45712a61-a1b3-41a4-aa89-9593b909ae3d\" -H \"content-type: application/json\" -H \"api-key: be23d9ccb29082c489ba629077553ba1d8314005\" -H \"username: sarahfranks\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"api_key\": \"pbkdf2:sha1:1000$vTuQRKeb$eec0bdffebde0d3c28290d41f4d848fbde04571c\",\n \"endpoint\": \"http://127.0.0.1:9090/account/endpoint\",\n \"epoch\": 1441164550.515677,\n \"id\": \"45712a61-a1b3-41a4-aa89-9593b909ae3d\",\n \"is_admin\": false,\n \"failed_count\": 0,\n \"secret_key\": \"5a4a1cf4895441a1dfaa504c471510be819198e7\",\n \"username\": \"sarahfranks\"\n }\n\n**Get all account records (admin only):**\n\nThis is a paginated call with ``start`` and ``limit`` params in the querystring.\n\n**REQUIRED** ``start`` is where in the records you want to start listing (0..n)\n\n**REQUIRED** ``limit`` is how many records to return\n\nIn the example below I started at record #0 and asked for up to 10 records to return.\nYou may also notice that a ``next_start`` field will show up in the JSON so you\nknow where to set your next start (assuming you want to keep paging the records)\n\n::\n\n curl -v -X GET \"http://127.0.0.1:8081/v1/accounts?start=0&limit=10\" -H \"content-type: application/json\" -H \"api-key: ba86c64c24f361ddbcfe27be187d8d3002c9f43c\" -H \"username: admin\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"accounts\": [\n {\n \"api_key\": \"pbkdf2:sha1:1000$rQDzv29j$5895b2393171d0cc238157c130fc2129d3e871c3\",\n \"endpoint\": \"\",\n \"epoch\": 1441164269.341982,\n \"id\": \"ed408f85-200e-481f-a672-30f454e8dcf4\",\n \"is_admin\": true,\n \"secret_key\": \"ab502753cbb68b90601cace345fe84fb2bb5b8dd\",\n \"username\": \"admin\"\n },\n {\n \"api_key\": \"pbkdf2:sha1:1000$I5r0MTsM$fc50fcce05c526fa19919d874087623571c0c9e0\",\n \"endpoint\": \"http://127.0.0.1:9090/account/endpoint\",\n \"epoch\": 1441164337.607172,\n \"id\": \"d969a56d-e520-405d-a24f-497ac6923781\",\n \"is_admin\": false,\n \"failed_count\": 0,\n \"secret_key\": \"2381a87ba4725786f29ca414d3217e202615f757\",\n \"username\": \"johndoe\"\n },\n {\n \"api_key\": \"pbkdf2:sha1:1000$an7K8KqL$127bb4796de21a832969512fc7c2edea0524e54b\",\n \"endpoint\": \"http://127.0.0.1:9090/account/endpoint\",\n \"epoch\": 1441164337.630147,\n \"id\": \"556daec0-fcad-4cae-8d4b-7564d2424669\",\n \"is_admin\": false,\n \"failed_count\": 0,\n \"secret_key\": \"25b83d9a713e16f1b4fe936787acdf532162ea73\",\n \"username\": \"janedoe\"\n },\n {\n \"api_key\": \"pbkdf2:sha1:1000$nbvEItNd$9d0ab21a122bca95855f6ba0ab271444168e17f4\",\n \"endpoint\": \"http://127.0.0.1:9090/account/endpoint\",\n \"epoch\": 1441164337.65272,\n \"id\": \"776236bc-5ca9-4083-bb20-b12043ec87de\",\n \"is_admin\": false,\n \"failed_count\": 0,\n \"secret_key\": \"d615166b1818ef41b925c40b5483474522bffc94\",\n \"username\": \"samjones\"\n },\n {\n \"api_key\": \"pbkdf2:sha1:1000$vTuQRKeb$eec0bdffebde0d3c28290d41f4d848fbde04571c\",\n \"endpoint\": \"http://127.0.0.1:9090/account/endpoint\",\n \"epoch\": 1441164550.515677,\n \"id\": \"45712a61-a1b3-41a4-aa89-9593b909ae3d\",\n \"is_admin\": false,\n \"failed_count\": 0,\n \"secret_key\": \"5a4a1cf4895441a1dfaa504c471510be819198e7\",\n \"username\": \"sarahfranks\"\n }\n ]\n }\n\nExample output with ``next_start``:\n\n::\n\n curl -v -X GET \"http://127.0.0.1:8081/v1/accounts?start=0&limit=3\" -H \"content-type: application/json\" -H \"api-key: 5b3a973f4980f65d5b61101ddf3b40808933f12a\" -H \"username: admin\"\n\n::\n\n {\n \"accounts\": [\n {\n \"api_key\": \"pbkdf2:sha1:1000$rQDzv29j$5895b2393171d0cc238157c130fc2129d3e871c3\",\n \"endpoint\": \"\",\n \"epoch\": 1441164269.341982,\n \"id\": \"ed408f85-200e-481f-a672-30f454e8dcf4\",\n \"is_admin\": true,\n \"secret_key\": \"ab502753cbb68b90601cace345fe84fb2bb5b8dd\",\n \"username\": \"admin\"\n },\n {\n \"api_key\": \"pbkdf2:sha1:1000$I5r0MTsM$fc50fcce05c526fa19919d874087623571c0c9e0\",\n \"endpoint\": \"http://127.0.0.1:9090/account/endpoint\",\n \"epoch\": 1441164337.607172,\n \"id\": \"d969a56d-e520-405d-a24f-497ac6923781\",\n \"is_admin\": false,\n \"failed_count\": 0,\n \"secret_key\": \"2381a87ba4725786f29ca414d3217e202615f757\",\n \"username\": \"johndoe\"\n },\n {\n \"api_key\": \"pbkdf2:sha1:1000$an7K8KqL$127bb4796de21a832969512fc7c2edea0524e54b\",\n \"endpoint\": \"http://127.0.0.1:9090/account/endpoint\",\n \"epoch\": 1441164337.630147,\n \"id\": \"556daec0-fcad-4cae-8d4b-7564d2424669\",\n \"is_admin\": false,\n \"failed_count\": 0,\n \"secret_key\": \"25b83d9a713e16f1b4fe936787acdf532162ea73\",\n \"username\": \"janedoe\"\n }\n ],\n \"next_start\": 3\n }\n\n**Update the endpoint field for a username specified account:**\n\nThe only field that can be updated on an account is the ``endpoint`` and when you\ndo so PyWebhooks will contact that endpoint with the echo challenge as mentioned above\nin the section on creating a new account.\n\n**Note:** The ``api_key`` and ``secret_key`` can both be reset, those calls are\nfurther down this document.\n\nFor this call you need to supply your username and ``api_key`` in the headers.\n\n::\n\n curl -v -X PATCH \"http://127.0.0.1:8081/v1/account\" -d '{\"endpoint\": \"http://127.0.0.1:9090/account/alternate/endpoint\"}' -H \"content-type: application/json\" -H \"api-key: d615166b1818ef41b925c40b5483474522bffc94\" -H \"username: samjones\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"deleted\": 0,\n \"errors\": 0,\n \"inserted\": 0,\n \"replaced\": 1,\n \"skipped\": 0,\n \"unchanged\": 0\n }\n\n**Delete a single account record:**\n\nUser's can only delete their account record.\n\n::\n\n curl -v -X DELETE \"http://127.0.0.1:8081/v1/account/776236bc-5ca9-4083-bb20-b12043ec87de\" -H \"content-type: application/json\" -H \"api-key: d615166b1818ef41b925c40b5483474522bffc94\" -H \"username: samjones\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"deleted\": 1,\n \"errors\": 0,\n \"inserted\": 0,\n \"replaced\": 0,\n \"skipped\": 0,\n \"unchanged\": 0\n }\n\n**Delete all account records (admin only):**\n\n**Careful:** This deletes all account records (except admin). The ``deleted``\nfield in the response will contain how many records were deleted.\n\n::\n\n curl -v -X DELETE \"http://127.0.0.1:8081/v1/accounts\" -H \"content-type: application/json\" -H \"api-key: f2fe92411648dab36532d4256a5d36be0b219d53\" -H \"username: admin\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"deleted\": 4,\n \"errors\": 0,\n \"inserted\": 0,\n \"replaced\": 0,\n \"skipped\": 0,\n \"unchanged\": 0\n }\n\n**Reset an account API key:**\n\nEnsure your service endpoint is running as the PyWebhooks server will perform a\n``GET`` against your endpoint with the new ``api_key`` in the querystring as:\n\n::\n\n GET /account/alternate/endpoint?api_key=768a8c2530956c0f2ac52faee785cadf3f5bc68d\n\n**Note:** A ``GET`` is used on the endpoint like the echo challenge since ``POST`` is\nused by incoming webhooks.\n\n::\n\n curl -v -X POST \"http://127.0.0.1:8081/v1/account/reset/apikey\" -H \"content-type: application/json\" -H \"username: sarahfranks\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"Message\": \"New key sent to endpoint\"\n }\n\n**Reset an account secret key:**\n\nEnsure your service endpoint is running as the PyWebhooks server will perform a\n``GET`` against your endpoint with the new ``secret_key`` in the querystring as:\n\n::\n\n GET /account/alternate/endpoint?secret_key=0d7929e61c97e10a70dd71cb839853bcd4f9e230\n\n**Note:** A ``GET`` is used on the endpoint like the echo challenge since ``POST`` is\nused by incoming webhooks.\n\n::\n\n curl -v -X POST \"http://127.0.0.1:8081/v1/account/reset/secretkey\" -H \"content-type: application/json\" -H \"username: johndoe\" -H \"api-key: 9241a57a6b4d785d7acb0fe9d99f7983f4d7584b\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"Message\": \"New key sent to endpoint\"\n }\n\nWebhook Actions\n^^^^^^^^^^^^^^^\n\nThe real essence of PyWebhooks is ultimately registering a webhook with the system\nand then having users/services subscribe to those webhooks and posting the data\nto your endpoint.\n\n**Creating a new webhook registration:**\n\nIn this example we will register the following webhook from the ``johndoe``\naccount.\n\n::\n\n {\n \"items\": [\n {\n \"item1\": 1\n },\n {\n \"item2\": 2\n }\n ],\n \"message\": \"hello world\"\n }\n\nThere are a few things you need to include in the JSON payload.\n\n``description`` is a user comsumable description of what your webhook is about\n``event_data`` is the actual JSON payload that will be delivered to each\nsubscribed user/service of this webhook when you trigger it\n``event`` is a header field that is a short description of what kind of event\nthis is\n\nThe full payload would be something like this:\n\n::\n\n {\n \"description\": \"This is my registered webhook\",\n \"event_data\": {\n \"items\": [\n {\n \"item1\": 1\n },\n {\n \"item2\": 2\n }\n ],\n \"message\": \"hello world\"\n },\n \"event\": \"mywebhook.event\"\n }\n\nCreate the webhook:\n\n::\n\n curl -v -X POST \"http://127.0.0.1:8081/v1/webhook/registration\" -H \"content-type: application/json\" -H \"username: johndoe\" -H \"api-key: ee98cb7b5da901c12bac7c263b28f7a028a5de97\" -d '{\"description\": \"This is my registered webhook\", \"event_data\": {\"items\": [{\"item1\": 1}, {\"item2\": 2}], \"message\": \"hello world\"}, \"event\": \"mywebhook.event\"}'\n\n**Response:**\n\n**HTTP/1.0 201 CREATED**\n\n::\n\n {\n \"account_id\": \"d969a56d-e520-405d-a24f-497ac6923781\",\n \"description\": \"This is my registered webhook\",\n \"epoch\": 1441166640.359496,\n \"event\": \"mywebhook.event\",\n \"event_data\": {\n \"items\": [\n {\n \"item1\": 1\n },\n {\n \"item2\": 2\n }\n ],\n \"message\": \"hello world\"\n },\n \"id\": \"3e25a22e-6a83-4cf0-a2bf-d7617aa32551\"\n }\n\n**Delete a webhook registration:**\n\nDeletes registration record, will also remove the records for this registration\nid in the subscription table as well.\n\n::\n\n curl -v -X DELETE \"http://127.0.0.1:8081/v1/webhook/registration/0c296ca8-69ce-4274-b377-3010072363f9\" -H \"content-type: application/json\" -H \"username: johndoe\" -H \"api-key: ee98cb7b5da901c12bac7c263b28f7a028a5de97\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"deleted\": 1,\n \"errors\": 0,\n \"inserted\": 0,\n \"replaced\": 0,\n \"skipped\": 0,\n \"unchanged\": 0\n }\n\n**Get all your registered webhook records:**\n\nLists all the calling username's registered webhooks.\n\nThis is a paginated call with ``start`` and ``limit`` params in the querystring.\n\n**REQUIRED** ``start`` is where in the records you want to start listing (0..n)\n\n**REQUIRED** ``limit`` is how many records to return\n\n::\n\n curl -v -X GET \"http://127.0.0.1:8081/v1/webhook/registration?start=0&limit=10\" -H \"content-type: application/json\" -H \"username: johndoe\" -H \"api-key: ee98cb7b5da901c12bac7c263b28f7a028a5de97\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"next_start\": 1,\n \"registrations\": [\n {\n \"account_id\": \"fb8854ba-b7f7-4552-bc13-4d5cdbb444dd\",\n \"description\": \"This is my registered webhook\",\n \"epoch\": 1441139002.671599,\n \"event\": \"mywebhook.event\",\n \"event_data\": {\n \"items\": [\n {\n \"item1\": 1\n },\n {\n \"item2\": 2\n }\n ],\n \"message\": \"hello world\"\n },\n \"id\": \"4618dc47-aaf9-401e-9aa4-8fda5d59eb25\"\n }\n ]\n }\n\n**Get all registered webhook records:**\n\nLists all registered webhooks.\n\nThis is a paginated call with ``start`` and ``limit`` params in the querystring.\n\n**REQUIRED** ``start`` is where in the records you want to start listing (0..n)\n\n**REQUIRED** ``limit`` is how many records to return\n\n::\n\n curl -v -X GET \"http://127.0.0.1:8081/v1/webhook/registrations?start=0&limit=2\" -H \"content-type: application/json\" -H \"username: johndoe\" -H \"api-key: ee98cb7b5da901c12bac7c263b28f7a028a5de97\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"next_start\": 2,\n \"registrations\": [\n {\n \"account_id\": \"a6903d9f-de93-4910-8d8c-06e22f434d05\",\n \"description\": \"Some description goes here\",\n \"epoch\": 1441138315.006409,\n \"event\": \"webhook.event.hello\",\n \"event_data\": {\n \"msg\": \"hello world\"\n },\n \"id\": \"ae8dc785-d4bf-4614-98a7-32dcf03314e8\"\n },\n {\n \"account_id\": \"fb8854ba-b7f7-4552-bc13-4d5cdbb444dd\",\n \"description\": \"This is my registered webhook\",\n \"epoch\": 1441139002.671599,\n \"event\": \"mywebhook.event\",\n \"event_data\": {\n \"items\": [\n {\n \"item1\": 1\n },\n {\n \"item2\": 2\n }\n ],\n \"message\": \"hello world\"\n },\n \"id\": \"4618dc47-aaf9-401e-9aa4-8fda5d59eb25\"\n }\n ]\n }\n\n**Delete all webhook registration records (admin only):**\n\n**Careful:** This deletes all registration records. The ``deleted``\nfield in the response will contain how many records were deleted.\n\n::\n\n curl -v -X DELETE \"http://127.0.0.1:8081/v1/webhook/registrations\" -H \"content-type: application/json\" -H \"api-key: ba86c64c24f361ddbcfe27be187d8d3002c9f43c\" -H \"username: admin\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"deleted\": 2,\n \"errors\": 0,\n \"inserted\": 0,\n \"replaced\": 0,\n \"skipped\": 0,\n \"unchanged\": 0\n }\n\n**Update a webhook registration record:**\n\nOnly the ``description`` field can be updated on an registration.\n\nMake sure to supply the webhook registration id as per the example.\n\n::\n\n curl -v -X PATCH \"http://127.0.0.1:8081/v1/webhook/registration/4618dc47-aaf9-401e-9aa4-8fda5d59eb25\" -d '{\"description\": \"New Description\"}' -H \"content-type: application/json\" -H \"api-key: ee98cb7b5da901c12bac7c263b28f7a028a5de97\" -H \"username: johndoe\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"deleted\": 0,\n \"errors\": 0,\n \"inserted\": 0,\n \"replaced\": 1,\n \"skipped\": 0,\n \"unchanged\": 0\n }\n\nSubscription Actions\n^^^^^^^^^^^^^^^^^^^^\n\n**Creating a subscription:**\n\nCreate a subscription for a registered webhook that you want to receive\nnotifications from when they are triggered.\n\n::\n\n curl -v -X POST \"http://127.0.0.1:8081/v1/webhook/subscription/ae8dc785-d4bf-4614-98a7-32dcf03314e8\" -H \"content-type: application/json\" -H \"api-key: ee98cb7b5da901c12bac7c263b28f7a028a5de97\" -H \"username: johndoe\"\n\n\n**Response:**\n\n**HTTP/1.0 201 CREATED**\n\n::\n\n {\n \"account_id\": \"fb8854ba-b7f7-4552-bc13-4d5cdbb444dd\",\n \"epoch\": 1441145067.959285,\n \"id\": \"cf20c039-6355-40b9-a601-cad4e79dbe52\",\n \"registration_id\": \"ae8dc785-d4bf-4614-98a7-32dcf03314e8\"\n }\n\n**Get all your subscription records:**\n\nLists all the calling username's subscription records.\n\nThis is a paginated call with ``start`` and ``limit`` params in the querystring.\n\n**REQUIRED** ``start`` is where in the records you want to start listing (0..n)\n\n**REQUIRED** ``limit`` is how many records to return\n\n::\n\n curl -v -X GET \"http://127.0.0.1:8081/v1/webhook/subscription?start=0&limit=5\" -H \"content-type: application/json\" -H \"username: johndoe\" -H \"api-key: ee98cb7b5da901c12bac7c263b28f7a028a5de97\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"subscriptions\": [\n {\n \"account_id\": \"fb8854ba-b7f7-4552-bc13-4d5cdbb444dd\",\n \"epoch\": 1441144968.505692,\n \"id\": \"9e596765-da94-46d2-9f9d-a4d7ecc374ab\",\n \"registration_id\": \"ae8dc785-d4bf-4614-98a7-32dcf03314e8\"\n },\n {\n \"account_id\": \"fb8854ba-b7f7-4552-bc13-4d5cdbb444dd\",\n \"epoch\": 1441145067.959285,\n \"id\": \"cf20c039-6355-40b9-a601-cad4e79dbe52\",\n \"registration_id\": \"ac18dc47-abf9-401e-8bb3-8fda5d51af48\"\n }\n ]\n }\n\n**Get all subscription records:**\n\nLists all subscriptions.\n\nThis is a paginated call with ``start`` and ``limit`` params in the querystring.\n\n**REQUIRED** ``start`` is where in the records you want to start listing (0..n)\n\n**REQUIRED** ``limit`` is how many records to return\n\n::\n\n curl -v -X GET \"http://127.0.0.1:8081/v1/webhook/subscriptions?start=0&limit=2\" -H \"content-type: application/json\" -H \"username: johndoe\" -H \"api-key: ee98cb7b5da901c12bac7c263b28f7a028a5de97\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"next_start\": 2,\n \"subscriptions\": [\n {\n \"account_id\": \"fb8854ba-b7f7-4552-bc13-4d5cdbb444dd\",\n \"epoch\": 1441144968.505692,\n \"id\": \"9e596765-da94-46d2-9f9d-a4d7ecc374ab\",\n \"registration_id\": \"ae8dc785-d4bf-4614-98a7-32dcf03314e8\"\n },\n {\n \"account_id\": \"fb8854ba-b7f7-4552-bc13-4d5cdbb444dd\",\n \"epoch\": 1441145067.959285,\n \"id\": \"cf20c039-6355-40b9-a601-cad4e79dbe52\",\n \"registration_id\": \"ae8dc785-d4bf-4614-98a7-32dcf03314e8\"\n }\n ]\n }\n\n**Delete a single subscription record:**\n\nDeletes subscription record.\n\n::\n\n curl -v -X DELETE \"http://127.0.0.1:8081/v1/webhook/subscription/bfbafaa0-5816-456d-9639-98023ec5dc2e\" -H \"content-type: application/json\" -H \"username: johndoe\" -H \"api-key: ee98cb7b5da901c12bac7c263b28f7a028a5de97\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"deleted\": 1,\n \"errors\": 0,\n \"inserted\": 0,\n \"replaced\": 0,\n \"skipped\": 0,\n \"unchanged\": 0\n }\n\n**Delete all subscription records (admin only):**\n\n**Careful:** This deletes all subscription records. The ``deleted``\nfield in the response will contain how many records were deleted.\n\n::\n\n curl -v -X DELETE \"http://127.0.0.1:8081/v1/webhook/subscriptions\" -H \"content-type: application/json\" -H \"api-key: ba86c64c24f361ddbcfe27be187d8d3002c9f43c\" -H \"username: admin\"\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\n::\n\n {\n \"deleted\": 4,\n \"errors\": 0,\n \"inserted\": 0,\n \"replaced\": 0,\n \"skipped\": 0,\n \"unchanged\": 0\n }\n\nTriggered Actions\n^^^^^^^^^^^^^^^^^\n\nThere are two actions that can be done:\n\n1. Trigger a webhook\n\n2. List all the triggered webhooks\n\n**Trigger a webhook:**\n\nUse a registration id to trigger the webhook (inserts a triggered record).\n\n::\n\n curl -v -X POST \"http://127.0.0.1:8081/v1/webhook/triggered/bfbafaa0-5816-456d-9639-98023ec5dc2e\" -H \"content-type: application/json\" -H \"api-key: ee98cb7b5da901c12bac7c263b28f7a028a5de97\" -H \"username: johndoe\"\n\n**Response:**\n\n**HTTP/1.0 201 CREATED**\n\n::\n\n {\n \"epoch\": 1441334032.467688,\n \"id\": \"7c9cfb5c-dd9b-47cc-8579-32e06337e0f9\",\n \"registration_id\": \"bfbafaa0-5816-456d-9639-98023ec5dc2e\"\n }\n\n**Get all triggered webhooks:**\n\nLists all triggered records.\n\nThis is a paginated call with ``start`` and ``limit`` params in the querystring.\n\n**REQUIRED** ``start`` is where in the records you want to start listing (0..n)\n\n**REQUIRED** ``limit`` is how many records to return\n\n::\n\n {\n \"triggered_webhooks\": [\n {\n \"epoch\": 1441333750.649395,\n \"id\": \"fc20ee3f-2278-4d14-1058-afab5b2c1b34\",\n \"registration_id\": \"bfbafaa0-5816-456d-9639-98023ec5dc2e\"\n },\n {\n \"epoch\": 1441333775.45855,\n \"id\": \"abf196cf-e3cd-47d5-9458-ecc22e5e1ae3\",\n \"registration_id\": \"3279b8af-3a90-4cf1-afb8-12872849b2ac\"\n },\n {\n \"epoch\": 1441333841.789931,\n \"id\": \"77c674fc-1907-499e-8e52-3faa57804977\",\n \"registration_id\": \"3279b8af-3a90-4cf1-afb8-12872849b2ac\"\n },\n {\n \"epoch\": 1441334032.467688,\n \"id\": \"7c9cfb5c-dd9b-47cc-8579-32e06337e0f9\",\n \"registration_id\": \"3279b8af-3a90-4cf1-afb8-12872849b2ac\"\n }\n ]\n }\n\n**Response:**\n\n**HTTP/1.0 200 OK**\n\nLicense\n^^^^^^^\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.",
"description_content_type": "",
"docs_url": null,
"download_url": "",
"downloads": {
"last_day": -1,
"last_month": -1,
"last_week": -1
},
"home_page": "https://github.com/chadlung/pywebhooks",
"keywords": "WebHooks",
"license": "",
"maintainer": "",
"maintainer_email": "",
"name": "pywebhooks",
"package_url": "https://pypi.org/project/pywebhooks/",
"platform": "",
"project_url": "https://pypi.org/project/pywebhooks/",
"project_urls": {
"Homepage": "https://github.com/chadlung/pywebhooks"
},
"release_url": "https://pypi.org/project/pywebhooks/0.5.5/",
"requires_dist": null,
"requires_python": "",
"summary": "WebHooks Service",
"version": "0.5.5"
},
"last_serial": 4800845,
"releases": {
"0.5.1": [
{
"comment_text": "",
"digests": {
"md5": "352fe19b2b089256a24d38f86f73426f",
"sha256": "6c9e0ebc9f58ae00978a8ffb43a7dab272bc645d939ddc2482d70065b8b8eb5f"
},
"downloads": -1,
"filename": "pywebhooks-0.5.1.tar.gz",
"has_sig": false,
"md5_digest": "352fe19b2b089256a24d38f86f73426f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 45009,
"upload_time": "2015-09-22T21:24:44",
"url": "https://files.pythonhosted.org/packages/97/b0/f54528b269df9d084a1242bcd35c28ca22d917e625b951185756b9f99421/pywebhooks-0.5.1.tar.gz"
}
],
"0.5.2": [
{
"comment_text": "",
"digests": {
"md5": "0e95e03c699a27d3632fc8eb7a0ee37a",
"sha256": "da89e6f6c7d94a6fafa9e5aa7f5894656d86fb92ba32607c51f11560f72148f6"
},
"downloads": -1,
"filename": "pywebhooks-0.5.2.tar.gz",
"has_sig": false,
"md5_digest": "0e95e03c699a27d3632fc8eb7a0ee37a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 45001,
"upload_time": "2015-09-22T21:29:10",
"url": "https://files.pythonhosted.org/packages/78/8c/b32d600f6d031d90970c02f946a1336433433c3a2ff1437e39c16f53198a/pywebhooks-0.5.2.tar.gz"
}
],
"0.5.3": [
{
"comment_text": "",
"digests": {
"md5": "15bdaf293e75bf03c7d3eb322411b276",
"sha256": "be980bdb21cad8bd6cebc1178f388fdb55f1684d7074d452561dec4394a45911"
},
"downloads": -1,
"filename": "pywebhooks-0.5.3.tar.gz",
"has_sig": false,
"md5_digest": "15bdaf293e75bf03c7d3eb322411b276",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 45005,
"upload_time": "2015-12-22T04:22:53",
"url": "https://files.pythonhosted.org/packages/6b/8d/157743d8926289f25c14b77800a2ac2d93d61b4ddf7d0b2d396c091df153/pywebhooks-0.5.3.tar.gz"
}
],
"0.5.4": [
{
"comment_text": "",
"digests": {
"md5": "2f194f8e169c8c5ec4962927f5fa3867",
"sha256": "2a068c8adf6e894634605d79b34a557c6a78fdf58e46864c74a31f1580fb69a8"
},
"downloads": -1,
"filename": "pywebhooks-0.5.4.tar.gz",
"has_sig": false,
"md5_digest": "2f194f8e169c8c5ec4962927f5fa3867",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 46634,
"upload_time": "2016-03-27T22:28:42",
"url": "https://files.pythonhosted.org/packages/bd/97/fbd34c032b34461e6d8631d90d538eb9a073ad608de0db7ea7343e164080/pywebhooks-0.5.4.tar.gz"
}
],
"0.5.5": [
{
"comment_text": "",
"digests": {
"md5": "4efa9803b441832f2f5cdd198c7aa986",
"sha256": "882bdd1b629fa401d91117316d06356dfaa8a4b8ed5805ef72d22c65bb0ca6c6"
},
"downloads": -1,
"filename": "pywebhooks-0.5.5.tar.gz",
"has_sig": false,
"md5_digest": "4efa9803b441832f2f5cdd198c7aa986",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 48197,
"upload_time": "2019-02-10T00:44:35",
"url": "https://files.pythonhosted.org/packages/e3/58/9828fdfe53de7fbfb9b3e4089c381231e27cb52e066c35216e7de7f6df35/pywebhooks-0.5.5.tar.gz"
}
]
},
"urls": [
{
"comment_text": "",
"digests": {
"md5": "4efa9803b441832f2f5cdd198c7aa986",
"sha256": "882bdd1b629fa401d91117316d06356dfaa8a4b8ed5805ef72d22c65bb0ca6c6"
},
"downloads": -1,
"filename": "pywebhooks-0.5.5.tar.gz",
"has_sig": false,
"md5_digest": "4efa9803b441832f2f5cdd198c7aa986",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 48197,
"upload_time": "2019-02-10T00:44:35",
"url": "https://files.pythonhosted.org/packages/e3/58/9828fdfe53de7fbfb9b3e4089c381231e27cb52e066c35216e7de7f6df35/pywebhooks-0.5.5.tar.gz"
}
]
}