Connect Patreon to aMember with Webhooks

Connect Patreon to aMember with Webhooks
aMember Membership

In this tutorial, you will learn how to connect Patreon with aMember for free using Webhooks. Once you have established this connection, users who pledge on Patreon will automatically gain access to your products on aMember.

Table of content

What are we going to build?

We are going to build an App that automates your Patreon and aMember workflow. When a new user pledges on your Patreon, the user will gain access to your aMember membership automatically.

Get your aMember product ID

Go to your aMember Pro admin dashboard, click on the Products menu, and choose Manage Products. From there, you should see a list of your products. Get the product ID to which you want the new Patreon to gain access. The product ID is the number in the first column of your product. If you don't already have a product, click on the New Product button to create one.

Amember products

Create aMember API key

Go to aMember CP -> Configuration -> Add-ons, and enable api module. If your aMember installation has no "api" module available, you can get it for free in the members area

Once the module is enabled, scroll down and find an admin menu item Remote Api Permissions. Click New Record.

You will see a form to fill - there will be a field for your comment, a generated access key itself, and list of checkboxes describing what system calls is available for given access key. Click on Check all and Save. Copy your API key from the page and save it somewhere securely.

aMember API

Set up a webhook server with Cloudflare Workers

In my previous tutorial A Beginner's Guide to Setting up Free Webhooks with Cloudflare Workers, I demonstrated how to set up a production-ready Webhook server that can be used for Patreon automation. We will catch up from there and build an App by adding business logic to that Webhook server. To get started, just create a new Worker and replace the code with the following one.

💡
Add the Webhook server URL to Patreon to get your Webhook secret key, replace the YOUR-SECRET_KEY_COPIED_FROM_PATREON with the secret key.
export default {
  async fetch(request, env) {

    try {
      if (request.method === "POST") {
        // get headers and request data
        let requestHeaders = Object.fromEntries(request.headers);
        const body = await request.json();

        // computing hex
        const encoder = new TextEncoder();
        const myText = encoder.encode(JSON.stringify(body));
        const secretKeyData = encoder.encode('YOUR_SECRET_KEY_COPIED_FROM_PATREON');
        const key = await crypto.subtle.importKey(
          'raw',
          secretKeyData,
          { name: 'HMAC', hash: 'MD5' },
          false,
          ['sign']
        );
        const mac = await crypto.subtle.sign('HMAC', key, myText);
        const hex = Array.prototype.map.call(new Uint8Array(mac), x => (('00' + x.toString(16)).slice(-2))).join('');

        // comparing signature
        if (request.headers.get('x-patreon-signature') !== hex) {
          // Webhook request did not come from Zoom
          console.log('Request not authorized');
          return new Response('Request not authorized', {
            status: 401
          })
        } else {
          // Webhook request came from Patreon
          console.log('Authorized request');
          return new Response('Authorized request', {
            status: 200
          })
        }

      }
      return new Response("Access Denied")
    } catch (e) { return new Response(err.stack, { status: 500 }) }
  }
}

Code snippet for Webhook server

Add business logic to the Webhook server

The business logic is: when a new user pledges on your Patreon, the user will then gain access to a membership on aMember site.

Let's update the code for the Webhook server, and replace the code with below. Please replace the variables AMEMBER_DOMAIN, ACCESS_KEY, and PRODUCT_ID with your own.

export default {
  async fetch(request, env) {
    const AMEMBER_DOMAIN = "YOUR_AMEMBER_DOMAIN"
    const ACCESS_KEY = "YOUR_AMEMBER_ACCESS_KEY"
    const PRODUCT_ID = "YOUR_AMEMBER_PRODUCT_ID"

    async function gatherResponse(response) {
      const { headers } = response;
      const contentType = headers.get("content-type") || "";
      if (contentType.includes("application/json")) {
        return JSON.stringify(await response.json());
      }
      return response.text();
    };

    try {
      if (request.method === "POST") {
        // get headers and request data
        let requestHeaders = Object.fromEntries(request.headers);
        const body = await request.json();

        // computing hex
        const encoder = new TextEncoder();
        const myText = encoder.encode(JSON.stringify(body));
        const secretKeyData = encoder.encode('YOUR_SECRET_KEY_COPIED_FROM_PATREON');
        const key = await crypto.subtle.importKey(
          'raw',
          secretKeyData,
          { name: 'HMAC', hash: 'MD5' },
          false,
          ['sign']
        );
        const mac = await crypto.subtle.sign('HMAC', key, myText);
        const hex = Array.prototype.map.call(new Uint8Array(mac), x => (('00' + x.toString(16)).slice(-2))).join('');

        // comparing signature
        if (request.headers.get('x-patreon-signature') !== hex) {
          // Webhook request did not come from Zoom
          console.log('Request not authorized');
          return new Response('Request not authorized', {
            status: 401
          })
        } else {
          // Webhook request came from Patreon
          console.log('Authorized request');
          // Extract email address of the new user from Patreon's update
          const email = body.included[0].attributes.email;
          // get aMember user id with email address by calling aMember API
          const checkURL = `http://${AMEMBER_DOMAIN}/api/check-access/by-email?_key=${ACCESS_KEY}&email=${email}`
          const response = await fetch(checkURL);
          const results = await gatherResponse(response);
          const userID = JSON.parse(results).user_id;
          // get today's date and set expiration data
          const today = new Date().toISOString().slice(0, 10);
          let expd = new Date();
          expd.setDate(new Date().getDate()+365);
          const exp = expd.toISOString().slice(0, 10);
          // add access to product with user ID and product ID
          const init = {
            method: "PUT",
            headers: {
              "content-type": "application/json;charset=UTF-8",
            },
          };
          const accessURL = `http://${AMEMBER_DOMAIN}/api/access/${userID}?_key=${ACCESS_KEY}&user_id=${userID}&product_id=${PRODUCT_ID}&begin_date=${today}&expire_date=${exp}`;
          const newResponse = await fetch(accessURL, init);
          const newResult = await gatherResponse(newResponse);
          
          return new Response('Authorized request', {
            status: 200
          })
        }

      }
      return new Response("Access Denied")
    } catch (e) { return new Response(err.stack, { status: 500 }) }
  }
}

Add the webhook server to Patreon and test it

Now let's add the Webhook server to Patreon and test it! Copy your Webhook server URL and paste it into your Patreon Webhook manager. In our example, the Webhook server URL is https://webhook.gludemo.workers.dev.

💡
Please be sure to update the Cloudflare Worker code with your real Webhook secret and variables.

Click the Send test button on the Create Pledge API call, and Patreon will push a test update to your Webhook server. The Webhook server will receive the update and add product access to the test user.

That's it! Now you have a free Webhook server with Cloudflare Workers that connect your Patreon account (as a creator) to your aMember site.

Wrapping up

In this tutorial, you have learned how to set up a production-ready webhook server with Cloudflare Workers. Connect your Patreon account (as a creator) to your aMember site. Should you have any questions, please feel free to login and comment here.