DEV Community

Cover image for Revue - Sendy sync: Webhook routes
Chris Bongers
Chris Bongers

Posted on • Originally published at daily-dev-tips.com

Revue - Sendy sync: Webhook routes

So far, we have been running scripts in an IIFE, which works fine for those that need to run every time we invoke it.

But for the last part, we need a route to which a webhook can post data.

We need these webhooks to support the Sendy callback on subscribe and unsubscribe.
We will create a route for those callbacks that will do the same action for the user on Revue.

If you want to follow along with the project, start from this GitHub branch.

Adding routes to our project

To make things easier for myself, I will use Fastify to handle my routes.
Fastify is a great project which doesn't take a lot of configuration, so we can focus on writing the actual content of the routes.

First, let's install the dependency.

npm install fastify
Enter fullscreen mode Exit fullscreen mode

Once installed, open up the index file and import the module.

import Fastify from 'fastify';

const fastify = Fastify({
  logger: true,
});
Enter fullscreen mode Exit fullscreen mode

The next step is to add our first route. Let's already call it subscribe.

fastify.get('/subscribe', function (request, reply) {
  reply.send({ hello: 'world' });
});
Enter fullscreen mode Exit fullscreen mode

Then we need to spool up the Fastify server.

fastify.listen({ port: 3000 }, function (err, address) {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }
});
Enter fullscreen mode Exit fullscreen mode

When you now run your server (node index.js), we should be able to visit http://localhost:3000/subscribe.

However, this now supports GET requests only, and our webhook performs a POST request.

These are easy changes as we can change the method on the Fastify route.

In the previous tests with the web hook request bin, we also learned the webhook returns which action is triggered, so we can rename our route to be one uniform route.

fastify.post('/sendy-webhook', function (request, reply) {
  reply.send({ hello: 'world' });
});
Enter fullscreen mode Exit fullscreen mode

Now we should be able to post to this webhook route.
Since we used our request bin in our initial testing, we know what the data object looks like.

{
  "trigger": "unsubscribe",
  "name": "",
  "email": "info@daily-dev-tips.com",
  "list_id": "xxx",
  "list_name": "DDT Subscribers",
  "list_url": "xxx",
  "gravatar": "xxx"
}
Enter fullscreen mode Exit fullscreen mode

Handling the webhook data

Let's modify our route to handle valid triggers.

fastify.post('/sendy-webhook', function (request, reply) {
  const data = request.body;
  if (!data.trigger) {
    throw new Error('Invalid data');
  }

  const { trigger, email } = data;
  if (['subscribe', 'unsubscribe'].includes(trigger)) {
    reply.send({ [trigger]: data.email });
  }

  throw new Error('Trigger not found');
});
Enter fullscreen mode Exit fullscreen mode

Let's restart our server and try the endpoint in our API platform.

POST request to our webhook endpoint

That seems to work perfectly.
When we created our Revue routes, we only supported the GET routes, but we need to post data for this one.

Let's modify our callRevueAPI to handle this.

const callRevueAPI = async (endpoint, method = 'GET', body) => {
  const response = await fetch(`https://www.getrevue.co/api/v2/${endpoint}`, {
    headers: {
      Authorization: `Token ${process.env.REVUE_API_TOKEN}`,
      'Content-Type': body
        ? 'application/x-www-form-urlencoded'
        : 'application/json',
    },
    method,
    body,
  }).then((res) => res.json());
  return response;
};
Enter fullscreen mode Exit fullscreen mode

This call defines which content type to set and passes the optional body.

Now we can modify our webhook to call this function like this.

if (['subscribe', 'unsubscribe'].includes(trigger)) {
  const url = `subscribers${trigger === 'unsubscribe' && '/unsubscribe'}`;
  const status = await callRevueAPI(url, 'POST', convertToFormData({ email }));
  return reply.send(status);
}
Enter fullscreen mode Exit fullscreen mode

We can use the same convertToFormData function we created before and simply post to the correct URL.
On execution, we return whatever Revue API returns to us.

I get the following response when trying this out in our API platform.

Revue API response

Excellent, we can see we get the correct response from Revue, and if we now check their system, we should see that the person is unsubscribed.

Person unsubscribed from Revue

Let's also try and see what happens on subscribe.

Subscribe callback from our webhook

And yes, the subscription also works as intended.

Conclusion

We set up a dynamic route by using Fastify. This handles aPOST request that can hold a uniform subscribe and unsubscribe callback.

We only have to host these scripts, and we should be ready to perform end-to-end tests.

You can also find the code for today's article on GitHub.

Thank you for reading, and let's connect!

Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter

Discussion (0)