Learn about `node-webhook-client`

Learn the basic functionality of the Weebly Node Webhook Client. Click the "Start" button below to see how Viewsaurus works.

Start Tutorial

Welcome!

The tutorial's navigation controls are located above this text. Use <i class="fa fa-fw fa-play"></i> to move to the next step of the tutorial. Use <i class="fa fa-fw fa-play fa-rotate-180"></i> to move to the previous step of the tutorial (if there is one). Use <i class="fa fa-fw fa-list"></i> to see an overview of all the steps in the tutorial.

What You Will Learn

  • How Weebly Node.js Webhook Client code operates.
  • How to subscribe your app to Weebly Webhook Events
  • How to properly handle Weebly Webhook Events

Register Your App

To obtain valid API Keys, you must first register your weebly app.

Please name this new app uniquely by using the following app naming convention (you will use this later in the tutorial as well): {{PREFIX}}-evt-sub

To ensure a unique naming patter, I recommend setting {{PREFIX}} to the initial of your first name + first three characters of your last name.

For example, if your name is "James Gunn", your {{PREFIX}} would be: jgun.

Set the 'category' of your new app to Services.

Usages

Please use the instructions Usages in the README.md file to setup this app for live operation and to see it in action.

About the Web Server

This app uses the Express.js web application framework. Since Express.js is well documented, this tutorial will not go into too many details, but there are some specific pieces of code you should understand.

Express.js allows developers to write middleware for their applications to customize how their applications handle requests and responses.

While it is outside of the scope of this tutorial, it is important to note, there are other types of middlware Express allows you to use:

Application-Level Middleware

Express.js allows developers to write middleware for their applications.

Highlighted is the instantiation of custom Express.js - Application-Level Middleware written specifically for the Weebly Node Webhook Client,

Next, take a look at the code-behind the Weebly Middleware...

Weebly Middleware

In case you are not familiar, the Weebly Middleware is a Node.js module that exports a function wrapper.

The wrapperfunction sets up some internal variables then ensures API Keys have been set either explicitly in code and supplied as arguments, or implicitly in your environment variables or from command line options.

Accessing API Keys in Requests

This particular middleware exposes your Weebly API Keys as properties of the request in Express.

This allows you to reference these values from within subsequent Router Middleware in the following manner:

  • req.app.clientId
  • req.app.clientSecret

Mounting Middleware

Mounting middleware into your app is relatively simple once it is defined, but the order is very important.

For each HTTP request your app will receives to: /oauth or /webhooks this code defines how it should be handled.

Specifically, you can see that each route has its own middleware chain and they both share the Weebly (Application-Level) Middleware, which is executed before the Router-Level Middleware for both routes.

  • Requests to /oauth[...] will be sent to the Weebly Middleware, and then to the OAuth Routing Middleware
  • Requests to /webhooks[...] will be sent to the Weebly Middleware, and then to the Webhook Routing Middleware

OAuth Middleware

oauth-router.js is custom Express.js Routing Middleware made specifically for Weebly.

It handles all incoming HTTP requests to /oauth (where '/' is the webroot of the app).

Please read the App Authorization and Installation Flow to understand each phase of the OAuth2 Authorization Flow that Weebly apps encounter during installation.

Oauth - Phase One

First, the code will accept HTTP GET requests going to /oauth/phase-one, and immediately defines the clientId and secretKey variables which were made available from the Weebly Middleware.

Request Validation

As a developer, you must KNOW with great confidence the inbound requests your web applications can trust.

In this section of code you can see how to invalidate requests to ensure that requests sent to this particular route can be trusted as coming from Weebly. Here, a comparison object is created and converted into a string (verification HMAC) using identical values to the Weebly generated HMAC. then you use these two pieces of data to invalidate the HMAC query parameter received in requests to this route, are actually requests you can trust were sent from Weebly servers.

You can read the details about this here, but to provide you the 'gist' of what happens:

  1. A Weebly site administrator (user) clicks the "Add App" button for your app in Weebly App Center
  2. Weebly presents the 'user' with the list of persmissions (scopes defined in your manifest.json) that your app is requesting
  3. The 'user' can either authorize (connect) or decline, but assume the 'user' accepts for sake of example
  4. Weebly creates an HMAC-SHA256 encoded hash of the request parameters using your Weebly App SECRET, which means ONLY YOU should be able to recreate that EXACT HMAC string using identical values
  5. If your generated HMAC string is IDENTICAL to the HMAC in the request query parameters, you can consider this a "verified request" (trusting that it came from Weebly) and proceed with code execution

Requesting an Authorization Code

Since the request is trusted, you first generate the second phase URL, callback parameters, and version (if included in the original request).

Then your app tells the Express response object to redirect to the appropriate Weebly URL to obtain an authorization_code.

If the redirect is successfully formed/accepted, Weebly will respond to your redirect by sending a new HTTP request to containing an authorization_code correlating to the Weebly User and Site.

Oauth - Phase Two

Here, the code designates it will accept HTTP GET requests going to /oauth/phase-two, and immediately defines the clientId and secretKey variables which were made available from the Weebly Middleware.

Exchanging Authorization Code for Access Token

NOTE: It is of CRITICAL IMPORTANCE you take every measure possible to secure and mitigate attackers from obtaining your Weebly API Keys (including your app manifest.json file).

There are a few things happening here to exchange the authorization_code for a valid access_token:

  1. The body is formed for the HTTP POST request which includes: authorization_code, client_id, client_secret
  2. The redirect_url parameter is used as the destination for the subsequent code-token exchange request
  3. App initiates POST request to redirect_url (Weebly API endpoint /app-center/oauth/access_token
  4. An access_token will be received upon a successful request, and should be stored securely by your app for future Weebly API requests

Connecting Your App

Many developers get confused about when their app is actually "installed" and "connected" to a Weebly site.

Logging new access_tokens to '/messages/messages.txt' file is not very useful or productive. What this section of code represents is the correct point-in-time during the App Authorization and Installation Flow when your app/service has an opportunity to ensure the most optimal User Experience by performing any:

  • Account/User setup
  • New User Verification(s)
  • Weebly Plan/Feature Checks
  • Security Checks

These types of service initializations should occur prior to your app redirecting to the final destination, which you can see in the next step of the tutorial.

Final Redirect - Connecting your App

To reach this point in the code, your app must have:

  1. Successfully validated an installation request from Weebly to your manifest.json callback_url property
  2. Redirected back to Weebly with the expected request/payload
  3. Successfully received an authorization_code from the phase-two portion of the OAuth2 Authorization Flow
  4. Redirected back to Weebly to exchange your authorization_code for a valid API access_token
  5. Executed any account/user setup or validations interally for your app or service

This final redirect indicates to Weebly that your app has completed all necessary setup and configuration operations, and the app should be considered "installed" and "connected".

If your app does not perform this final redirect, your app will be in a disconnected state.

Webhooks Middleware

webhooks-router.js is custom Express.js Routing Middleware handling all incoming HTTP requests beginning with /webhooks.

The URL where events will be sent is defined in webhooks.callback_url property of your manifest.json.

Please read the Receiving Webhooks to understand the data Weebly will POST to your app when events occur to which your app has subscribed.

Subscribing to Events

Weebly apps can subscribe to events. There are several different types of events available for your app to be notified about when they occur upon a site where your app is installed:

You define the types of Weebly Events your app will receive in the webhooks.events property in your manifest.json.

The default events this app subscribes to are: app.uninstall, user.update, site.publish, site.delete. These are all crucial events for any Weebly App and are highly recommended for subscription.

Subscribing to Events

The code here indicates this app will only HTTP POST requests sent to:

{{YOUR_APP_ROOT_URL}}/webhooks/callback will be accepted, but you still need to peform request invalidation to be certain Weebly sent the request.

Invalidating Webhook Event Requests

To prevent malicious attackers from spoofing or forging requests to your webhook.callback_url you need to invalidate the request.

All Webhook Event POST Request Body will contain:

  • client_id: Your app's Client ID from the Weebly Developer Admin Portal
  • client_version: The version of your app which generated the event (helpful in cases of managing multiple versions)
  • event: The event name (according to the Weebly Webhook docs)
  • timestamp: Unix epoch timestamp when the event occurred according to Weebly servers
  • data: Event-specific data, see the respective Webhook Event Type docs
  • hmac: client_id,client_version,event,timestamp, anddata` sent in the request to be used to invalidate incoming event requests

Similar to verifying the HMAC hash received on app installation, here we prepare a comparison object/string and then re-generate the HMAC to be certain the request was actually created and sent by Weebly.

Handling Events

Here, the code is merely logging the event headers and requests to console, but this is an opportunity for your app to become event-driven!

In production apps, this is the point-in-time to handle events allowing your app to behave proactively, addressing concerns such as:

  • Saving events to persistent storage (logging)
  • Making relevant Weebly API requests based on your app business logic
  • Performing internal data update(s)for the respective customer/site
  • Revoking access/permissions (in cases of app.uninstall or site.delete events)
  • Alerting/Reporting to customer(s) of related changes within your app/service

Webhook Event Responses

When your app has received a valid and verified Weebly Event Request, it is expected that your server will respond with an HTTP 200 Status Code before the request times out.

If you respond with anything other than an HTTP 200 to a valid Weebly Webhook Event Request, Weebly will continue to retry sending the Webhook event to your server up to 12 times over a 48 hour period.

Custom Event Handlers

A natural next step is for you to develop custom event handlers that interoperate with your business systems.

This app is already subscribed to: app.uninstall, site.publish, site.delete, and user.update events.

How would you implement custom handlers for each of these types of events?

Persistent Data Storage

You'll want to have a persistent data store to handle:

  • App Installation states (OAuth2 Authorization Flow)
  • Site Publish Events (especially if you have developed a Weebly Element App.
  • Deactivate or delete App Installations when you receive new app.uninstall or site.delete events
  • Prevent data loss when this app is unloaded from memory

Share Your Feedback

To help us improve this tutorial, and the code we open source, we need your feedback: