diff --git a/_posts/2015-04-05-webhooks.md b/_posts/2015-04-05-webhooks.md index 82dc9a39..19e9d206 100644 --- a/_posts/2015-04-05-webhooks.md +++ b/_posts/2015-04-05-webhooks.md @@ -5,98 +5,316 @@ category: labs date: 2015-04-05 11:42:28 --- +This is a node.js application that listens for all events on BigBlueButton and sends POST requests +with details about these events to hooks registered via an API. A hook is any external URL that can +receive HTTP POST requests. -(DRAFT) -# Introduction +These web hooks allow 3rd party applications to subscribe to different events that happen during a +BigBlueButton session. An event can be: a meeting was created, a user joined, a new presentation was +uploaded, a user left, a recording is being processed, and many others. You can see the entire list +of events that generate web hooks calls in [this file](https://github.com/bigbluebutton/bigbluebutton/blob/master/labs/bbb-webhooks/config.coffee) +(search for `config.hooks.events`). -The following page describes the specification of the Webhook API for BigBlueButton. +Registering hooks: API calls +---------------------------- -This will allow a 3rd party application subscribe to the different events that happen during a BigBlueButton session. Events such as user join, user leaves, etc. +This application adds three new API calls to BigBlueButton's API. -**Currently, this is in development phase, it's not yet a working prototype or even a production feature. These instructions are only for developers.** +### Hooks/Create -# Setting up +Creates a new hook. This call is idempotent: you can call it multiple times with the same parameters +without side effects (just like the `create` call for meetings). +Can optionally receive a `meetingID` parameter: if informed, this hook +will receive only events for this meeting; otherwise the hook will be global and will receive +events for all meetings in the server. -## Get the latest code -In a BigBlueButton git repo, do: - + + +### Hooks/Destroy + +Remove a previously created hook. A `hookID` must be passed in the parameters to identify +the hook to be removed. + +**Resource URL:** `http://yourserver.com/bigbluebutton/api/hooks/destroy?[parameters]&checksum=[checksum]` + +**Parameters**: + +| Param Name | Required / Optional | Type | Description | +| ----------- | -------------------- | ----- | ----------- | +| hookID | Required | Number | The ID of the hook that should be removed, as returned in the create hook call. | + +**Response when a hook is successfully removed**: + +```xml + + SUCCESS + true + ``` -git checkout merge-polling-with-master -git pull origin merge-polling-with-master + +**Response when a hook is not found**: + +```xml + + FAILED + destroyMissingHook + The hook informed was not found. + ``` -## Compile and Build -You will need to compile and deploy BigBlueButton-Web. After that, install Node.js (check out labs/bbb-callbacks/README for instructions). +**Response when a hook is not passed in the parameters**: -Then run the webhook module: +```xml + + FAILED + missingParamHookID + You must specify a hookID in the parameters. + ``` -cd labs/bbb-callbacks -npm install -d -nohup node app.js > output.log & + +**Response when there was an error removing the hook**: + +```xml + + FAILED + destroyHookError + An error happened while removing your hook. Check the logs. + ``` -# API Description -We have created three new API calls in order to enable webhooks: +### Hooks/List -Name: **subscribeEvent** -Parameters: -| Param | Description | -|:------|:------------| -| meetingID | String. the meeting that you would to check the events | -| callbackURL | String. It's the URL of your third party application that will handle the events | -Response: Status and SubscriptionID +Returns the hooks registered. If a `meetingID` is informed, will return the hooks created +specifically for this meeting plus all the global hooks (since they also receive events for +this `meetingID`). If no `meetingID` is informed, returns all the hooks available (not +only the global hooks, as might be expected). -Name: **unsubscribeEvent** -Parameters: -| Param | Description | -|:------|:------------| -| meetingID | String. the meeting that you would to check the events | -| subscriptionID | Identifier. It's the identifier of the subscription | -Response: Status +**Resource URL:** `http://yourserver.com/bigbluebutton/api/hooks/list?[parameters]&checksum=[checksum]` -Name: **listSubscriptions** -Parameters: -| Param | Description | -|:------|:------------| -| meetingID | String. the meeting that you would to check the events | -Response: List of the subscriptions with their status +**Parameters**: -# Usage -## EndPoint service -For testing this feature, you will need to have already the 3rd party application for handling the events. If you don't have it, you can use the following service: http://postcatcher.in/ +| Param Name | Required / Optional | Type | Description | +| ----------- | -------------------- | ----- | ----------- | +| meetingID | Optional | String | A meeting ID to restrict the hooks returned only to the hooks that receive events for this meeting. Will include hooks that receive events for this meeting only plus all global hooks. | -This service will help you to display all the events that you would like to track down. PostCatcher will give you an unique endpoint URL that you can use for testing. The URL should be in this format: "http://postcatcher.in/catchers/XXX" +**Response when there are hooks registered**: -## Generate API Calls -Like this is a development feature, you will need to create the URLs for the API calls. However, you can also try the following tool which is based on the library of the [API-Mate](http://mconf.github.io/api-mate/) tool. +```xml + + SUCCESS + + + 1 + http://postcatcher.in/catchers/abcdefghijk + my-meeting + + + 2 + http://postcatcher.in/catchers/1234567890 + + + + +``` + +**Response when there are no hooks registered**: + +```xml + + FAILED + + +``` -For example, for generate the subscribeEvent API Call: - * Fill in the fields according to your server - * Click "Custom parameters" to open the custom section - * Under "Custom API calls" write "call1=subscribeEvent" - * Under "Custom parameters" write param `callbackURL=PostCatcher-EndPoint-URL` -## Testing -You will need to create a BigBlueButton session, then subscribe to the Meeting Events, and finally test with a generated event. +Callback format +--------------- -Click on the create link, then click on the custom api call which should have the name that you put in the previous step, in this case: "call1". +All hooks registered are called via HTTP POST with all the information about the event in +the body of this request. The request is sent with the `Content-type` HTTP header set to +`application/x-www-form-urlencoded` and the content in the body has the following format: -Now, it's time to test with a generated event. +``` +event={"header":{},"payload":{}} +timestamp=1415900488797 +``` -We still need to do changes in BigBlueButton-apps which is the component that handle the realtime conference. However, you can generate a redis pubsub event in order to test the webhook feature. This can be a little tricky. +The attribute `timestamp` is the timestamp of when this callback was made. If the web hooks +application tries to make a callback and it fails, it will try again several times, always +using the same timestamp. Timestamps will never be the same for different events and the +value will always increase. -Once that you created the meeting, you need to look for the internal meeting ID, which you can find in the API logs. After you find the internalMeetingID, go to the console and type: +The attribute `event` is a stringified version of all the data from the event as received +from redis. The data varies for different types of events, check the documentation for +more information. + +This is an example of the data sent for a meeting destroyed event: ``` -redis-cli -# Now in the redis console -redi> publish "bigbluebutton:events" "{\"data\":\"hola ola\",\"meetingID\":\"your-meeting-id\",\"event\":\"TestEvent\"} +event={"payload":{"meeting_id":"82fe1e7040551a6044cf375d12d765b5f0f099a4-1415905067841"},"header":{"timestamp":17779896,"name":"meeting_destroyed_event","current_time":1415905177220,"version":"0.0.1"}} +timestamp=1415900488797 ``` -This will generated a `TestEvent` which will be read it by the webhooks module and then it will perform a POST request to the EndPoint URL that you passed. Now, go to the PostCatcher site and you will see the generated event. +Moreover, the callback call is signed with a checksum, that is included in the URL of the +request. If the registered URL is `http://my-server.com/callback`, it will receive the +checksum as in `http://my-server.com/callback?checksum=yalsdk18129e019iklasd90i`. + +The way the checksum is created is similar to how the checksums are calculated for +the other BigBlueButton's API calls (take a look at [`setConfigXML`](/dev/api.html#setconfigxml)). + +``` +sha1(++) +``` + +Where: + +* ``: The original callback URL, that doesn't include the checksum. +* ``: All the data sent in the body of the request, concatenated and joined by `&`, as if they were parameters in a URL. +* ``: The shared secret of the BigBlueButton server. + +So, upon receiving a callback call, an application could validate the checksum as follows: + +* Get the body of the request, as in the example below: + + ``` +event={"header":{},"payload":{}} +timestamp=1234567890 + ``` + + And convert it to a string like in the example below: + + ``` +event={"header":{},"payload":{}}×tamp=1234567890 + ``` + +* Concatenate the original callback URL, the string from the previous step, and the BigBlueButton's salt. +* Calculate a `sha1()` of this string. +* The checksum calculated should equal the checksum received in the parameters of the request. + + +More details +------------ +* Callbacks are always triggered for one event at a time and in order. They are ordered the same way + they appear on redis pubsub (which might not exactly be the order indicated by their timestamps). + The timestamps will almost always be ordered as well, but it's not guaranteed. +* The application assumes that events are never duplicated on pubsub. If they happen to be + duplicated, the callback calls will also be duplicated. +* Hooks are only removed if a call to `/hooks/destroy` is made or if the callbacks for the hook fail too + many times (~12) for a long period of time (~5min). They are never removed otherwise. Valid for + both global hooks and hooks for specific meetings. So it's recommended for 3rd-party applications + to register the hooks more than just once. You can either check if your hook is registered with + `/hooks/list` and register it if it isn't, or simply register your hook every e.g. 5 minutes. +* The application uses internal mappings to find out to which meeting the events received from redis + are related to. These mappings are removed after 24 hours of inactivity. If there are no events at + all for a given meeting during this period, it will be assumed dead. This is done to prevent data + from being stored forever on redis. This means that you can have issues if you have a hook + registered for an specific meeting (doesn't happen for global hooks) and this meeting happens to + not generate events for 24 hours, but it's still valid after it. Something very, very unlikely to + happen! +* External URLs are expected to respond with the HTTP status 2xx (200 would be the default expected). + Anything different from these values will be considered an error and the callback will be called + again. This includes redirects: if your hook redirects the request, it will be considered as + invalid and the web hooks application will try to call this hook again. +* If a meeting is created while the web hooks application is down, callbacks will never be triggered + for this meeting. The application needs to detect the create event (`meeting_created_message`) to + have a proper mapping of internal to external meeting IDs. So make sure the web hooks application + is always running while BigBlueButton is running! +* If you register a hook with, for example, the URL "http://myserver.com/my/hook" and no `meetingID` + set (making it a global hook) and later try to register another hook with the same URL but with + a `meetingID` set, the first hook will not be removed nor modified, while the second hook will + not be created. + + +Test it +------- + +The easiest way to test the web hooks application is to register hooks in your BigBlueButton server +using the [API Mate](http://mconf.github.io/api-mate/) and capture the callbacks using the +service [PostCatcher](http://postcatcher.in/). + +Follow the steps: + +* Open [PostCatcher](http://postcatcher.in/) and click on "Start testing your POST requests now" + +* It will redirect you to an URL such as `http://postcatcher.in/catchers/5527e67ba4c6dd0300000738`. + Save this URL to use later. + +* Open the [API Mate](http://mconf.github.io/api-mate/) and configure your server and shared secret. + +* On the menu "Custom parameters", there's a field "Custom API calls:". Add these values to it: + + ``` +hooks/create +hooks/list + ``` + +* On the same menu section, add the following values to "Custom parameters:": + + ``` +callbackURL=http://postcatcher.in/catchers/5527e67ba4c6dd0300000738 + ``` + + Modify this URL to the URL you got from PostCatcher earlier. + +* On the API Mate, click on the "custom call: hooks/create" link. It should respond with a success + message. + +* On the API Mate, click on the "custom call: hooks/list" link to check if your hook was really + registered. + +* Create a meeting and join it using the API Mate. + +* Do stuff inside the meeting and check your PostCatcher page, you should see events pop up on it + as you interact in your meeting. + +Development +----------- + +See https://github.com/bigbluebutton/bigbluebutton/tree/master/labs/bbb-webhooks#development