How to Receive Webhook Events With Netlify Functions and JavaScript

If you're building event-driven applications, you've probably considered building them in a Serverless platform. It's really well suited for having small functions, running when there's an incoming event, processing the data, and then going dormant. Instead of building your own event loop that sits idle in between events, you're offloading all that logic to a serverless provider. Being a lazy developer, I'm a fan of writing as little code as possible 😅.

Therefore, it won't come as a shock that most of my webhooks are running on serverless instead of on my local machine, or on a VM I have to maintain somewhere. We've recently been using Netlify here at Fidel for our documentation previews, so I thought I'd give Netlify Functions a try. In this blog post, I'm going to use them with JavaScript to receive webhook events from an API.

What Are Webhooks?

Before we begin, let's do a little refresher on what webhooks really are. If you think about APIs as black boxes that allow you to call them up once in a while, webhooks are the mechanism they use to call you back when they've got stuff to tell you. When you want to ask an API to do things for your application, you make an HTTP request to the API endpoint, and send some data. When an API wants to send some data to your application, without you having to ask for it every second (also known as polling), they make an HTTP request to your webhook URL.

Now that we know what webhooks are, let's look at a few things you'll need to follow along this journey of receiving webhook events with Netlify Functions and JavaScript.

Prerequisites

Creating a Netlify Function

Set Up Netlify CLI

I'm going to use the Netlify CLI to create and deploy a Function. In case you don't have it installed, now would be a good time to do so.

npm install netlify-cli -g

After you've installed the CLI, you'll also need to authenticate it with your Netlify account.

netlify login

This will open a browser window, asking for permission to access Netlify on your behalf. Go ahead and authorize that.

We'll also need to create a netlify.toml file in the root of your project folder, and we'll enable functions there.

[build]
  functions = "functions"
  publish = "dist"

Create a Netlify Function

Now that we're all set up with Netlify, we can go ahead and create a Function with the CLI.

netlify functions:create

This will create an interactive prompt, asking you for a template. I've chosen [hello-world], which is a basic JavaScript function that shows async/await usage, and formats your response. The prompt also asks for a name for your function, I've named mine webhook. The output of my command looked a little bit like this:

$ netlify functions:create
? Pick a template js-hello-world
? name your function:  webhook
â—ˆ Creating function webhook
â—ˆ Created /Users/laka/fidel/fidel-webhooks-netlify/functions/webhook/hello-world.js

The command also created a webhook.js file in the /functions/webhook/ folder. That was boilerplate code, and I've changed it a bit. I'm removing the Hello World code, and instead of logging the request body, and sending back a 200 OK status on the response. The logic is all wrapped in a try/catch that sends a 500 status and the error on the response in case something happened with the request. Which it shouldn't, but better safe than sorry. Most APIs have a retry mechanism for webhooks, so if we send back anything other than a 200, the API is going to resend the webhook later.

exports.handler = async (event, context) => {
  try {
    console.log(event.body)
    return {
      statusCode: 200
    }
  } catch (err) {
    return { statusCode: 500, body: err.toString() }
  }
}

Deploying a Netlify Function

This is the most basic webhook example I could think of. It logs or saves the event coming from an API, and sends back a confirmation code. What you do inside of that webhook should be based on your application logic, I'm showing you here the mechanism for this transport pattern.

The code needs to be deployed before we can use it, so let's go ahead and use the Netlify CLI to do that.

netlify deploy --prod

After the deploy finished, the output of my terminal looked a bit like this:

$ netlify deploy --prod   
Deploy path:        /Users/laka/fidel/fidel-webhooks-netlify/dist
Functions path:     /Users/laka/fidel/fidel-webhooks-netlify/functions
Configuration path: /Users/laka/fidel/fidel-webhooks-netlify/netlify.toml
Deploying to main site URL...
✔ Finished hashing 1 files and 1 functions
✔ CDN requesting 0 files and 1 functions
✔ Finished uploading 1 assets
✔ Deploy is live!

Logs:              https://app.netlify.com/sites/fidel-webhooks/deploys/5f19b5e49db36302958eeefe
Unique Deploy URL: https://5f19b5e49db36302958eeefe--fidel-webhooks.netlify.app
Website URL:       https://fidel-webhooks.netlify.app

Once the deploy is live, you'll be able to access your webhook logs in your Netlify dashboard. If you select your webhook there, you'll see the log window refresh in real-time, and that's where you'll find your function endpoint as well. The naming convention Netlify uses is https://your-app.netlify.app/.netlify/functions/your-function. Mine was https://fidel-webhooks.netlify.app/.netlify/functions/webhook.

Set Up for the Fidel API

Your webhook is live and ready to receive events, the only thing missing is an API to actually send those events 😅. Don't worry, I've got you covered with an example of using the Fidel API to send transaction authorization events to your webhook.

The Fidel API is providing real-time transaction data about purchases made on a card issued by Visa, Mastercard or Amex. Because of that real-time component, it wouldn't be wise to keep polling it every second or so to see if there are any new transactions. The API implements a webhook mechanism to send that data to your application whenever a new event happens. It actually supports quite a few webhooks for different types of events, but I won't dig into that here. We'll just focus on transactions for now.

Before we begin, you'll need to grab your Fidel API Key from the Dashboard. I'm using my Test Key for this, I want to be able to simulate transactions. It should look similar to sk_test_50ea90b6-2a3b-4a56-814d-1bc592ba4d63.

The API needs you to set up some plumbing before you can receive transactions, and we're going to use cURL commands to do that setup, instead of pointing and clicking in the dashboard. If you're already a Fidel user and have registered a program, brand, location and card, feel free to skip these steps - go straight to registering your webhook with the Fidel API.

The container for your transactions in the Fidel world is a Program. We'll start by creating one. Don't forget to replace fidel-key with your own before you run the cURL command.

curl -X POST \
  https://api.fidel.uk/v1/programs \
  -H 'content-type: application/json' \
  -H 'fidel-key: sk_test_50ea90b6-2a3b-4a56-814d-1bc592ba4d63' \
  -d '{
    "name": "Avocados"
  }'

The command outputs a JSON response from the API, with data about the program we created. We'll look for the id of it and make a note, we'll be using it later on.

{"items":[{"accountId":"3693ac7e-3e2b-432c-8c60-2b786453ca9b","live":false,"name":"Avocados","syncStats":{"status":"completed"},"updated":"2020-07-24T12:03:00.251Z","created":"2020-07-24T12:03:00.251Z","id":"08a09745-1e75-4ac3-baaf-f8548c31b25e","active":true,"activeDate":"2020-07-24T12:03:00.251Z","sync":false}],"resource":"/v1/programs","status":201,"execution":79.233234}

Now that we have a Program, we'll also need to create a Brand for our program.

curl -X POST \
  https://api.fidel.uk/v1/brands \
  -H 'content-type: application/json' \
  -H 'fidel-key: sk_test_50ea90b6-2a3b-4a56-814d-1bc592ba4d63' \
  -d '{
    "name": "Bacon Avocados"
  }'

Here's the output for that command. We'll make a note of the id for the brand as well, we'll need to use it later on.

{"items":[{"accountId":"3693ac7e-3e2b-432c-8c60-2b786453ca9b","consent":true,"live":false,"name":"Bacon Avocados","updated":"2020-07-24T12:05:35.868Z","created":"2020-07-24T12:05:35.868Z","id":"59ded730-007e-43a6-8547-7612d31355cb"}],"resource":"/v1/brands","status":201,"execution":15.915342}

Now that we have a brand and a program, we can create a Location for that brand. That location represents a physical store, so we can later simulate a live transaction originating there. We'll use the Program id we got from the previous command, and replace it in the URL. We'll also use the brandId in the request body to link the location to the Bacon Avocados brand.

curl -X POST \
  https://api.fidel.uk/v1/programs/08a09745-1e75-4ac3-baaf-f8548c31b25e/locations \
  -H 'content-type: application/json' \
  -H 'fidel-key: sk_test_50ea90b6-2a3b-4a56-814d-1bc592ba4d63' \
  -d '{
    "address": "2 Avocado Square", 
    "brandId": "59ded730-007e-43a6-8547-7612d31355cb", 
    "city": "London", 
    "countryCode": "GBR",
    "postcode": "W1D 3PX",
    "searchBy": {
        "merchantIds": {
            "visa": ["1234567","7654321"],
            "mastercard": ["1234567","7654321"]
        }
    }
}'

The output for this command has slightly more data, and that's because the location has information for each card network scheme available. We'll need to make a note of the id as well, that's what we're going to use to identify this location when we're making a transaction.

{"items":[{"accountId":"3693ac7e-3e2b-432c-8c60-2b786453ca9b","address":"2 Avocado Square","brandId":"59ded730-007e-43a6-8547-7612d31355cb","city":"London","countryCode":"GBR","currency":"GBP","live":false,"postcode":"W1D3PX","programId":"08a09745-1e75-4ac3-baaf-f8548c31b25e","geolocation":{"latitude":51.5138332,"longitude":-0.1318224},"preonboard":false,"searchBy":{"merchantIds":{"visa":["1234567","7654321"],"mastercard":["1234567","7654321"]}},"timezone":"Europe/London","updated":"2020-07-24T12:10:17.533Z","created":"2020-07-24T12:10:17.533Z","id":"fe77e7f5-350b-4c34-be68-3e16e7c95d66","amex":{"clearing":false,"auth":false,"authTransactionId":null,"clearingTransactionId":null,"status":"active"},"mastercard":{"clearing":false,"auth":false,"authTransactionId":null,"clearingTransactionId":null,"status":"active"},"visa":{"clearing":false,"auth":false,"authTransactionId":null,"clearingTransactionId":null,"status":"active"},"activeDate":"2020-07-24T12:10:17.533Z","active":true}],"resource":"/v1/programs/08a09745-1e75-4ac3-baaf-f8548c31b25e/locations","status":201,"execution":55.277626}

Now that we've set up our location, the only piece missing is a card to simulate a transaction from this location. We'll need to replace the program id in the URL here as well. We're not going to register an actual card here, we're using test cards. Depending on the card network we want to use, there are different ranges of card numbers we can use. For example, I'll use a test Visa card. Those follow a wildcard format in the range of 4444 0000 0000 4***. I'll use 4444000000004001 as my card number. The cards API also uses a different key for authorization, so instead of using your Secret API Key, you'll need to use your Public SDK Key from the Dashboard. It looks similar to the other one, the main difference is it starts with pk instead of sk. My example uses pk_test_62f02030-0409-4eb5-ab94-6eff05b3d888.

curl -X POST \
  https://api.fidel.uk/v1/programs/08a09745-1e75-4ac3-baaf-f8548c31b25e/cards \
  -H 'content-type: application/json' \
  -H 'fidel-key: pk_test_62f02030-0409-4eb5-ab94-6eff05b3d888' \
  -d '{
    "number": "4444000000004222",
    "expMonth": 10,
    "expYear": 2025,
    "countryCode": "GBR",
    "termsOfUse": true
}'

The output from the command should give us an id for the card, and we'll make a note of it. That's what we'll need when we make a transaction.

{"items":[{"accountId":"3693ac7e-3e2b-432c-8c60-2b786453ca9b","countryCode":"GBR","expDate":"2025-10-31T23:59:59.999Z","expMonth":10,"expYear":2025,"firstNumbers":"444400","lastNumbers":"4222","live":false,"programId":"08a09745-1e75-4ac3-baaf-f8548c31b25e","scheme":"visa","type":"visa","updated":"2020-07-24T12:28:16.957Z","created":"2020-07-24T12:28:16.957Z","id":"bb9b4a67-203c-4eae-8b09-070e819629cc"}],"resource":"/v1/programs/08a09745-1e75-4ac3-baaf-f8548c31b25e/cards","status":201,"execution":47.026675}

Register Webhook

We've set up everything we needed to start receiving transactions via the Fidel API. But for now, they would only show up in the Fidel Dashboard. If we want to use the real-time transactions in our application, we need to register a webhook URL for them. As I mentioned earlier, the Fidel API supports quite a few different webhooks. We're going to use the transaction.auth event today, and that fires when a transaction is being authorized. That usually happens as soon as you use your card in person or online to shop. Don't forget to replace the program id in the URL with your own. And use your own Netlify webhook URL in the request payload.

curl -X POST \
  https://api.fidel.uk/v1/programs/08a09745-1e75-4ac3-baaf-f8548c31b25e/hooks \
  -H 'content-type: application/json' \
  -H 'fidel-key: sk_test_50ea90b6-2a3b-4a56-814d-1bc592ba4d63' \
  -d '{
    "event": "transaction.auth",
    "url": "https://fidel-webhooks.netlify.app/.netlify/functions/webhook"
  }'

The JSON response from the API should be output to the terminal. It looks similar to this:

{"items":[{"accountId":"3693ac7e-3e2b-432c-8c60-2b786453ca9b","event":"transaction.auth","live":false,"programId":"08a09745-1e75-4ac3-baaf-f8548c31b25e","url":"https://fidel-webhooks.netlify.app/.netlify/functions/webhook","updated":"2020-07-24T12:39:15.131Z","created":"2020-07-24T12:39:15.131Z","id":"df1ab75a-04f9-4627-9b0a-c08cd28572e5","secretKey":"wh_ta_425e3be6-d7e3-4ad4-b747-5d5c498f171b"}],"resource":"/v1/programs/08a09745-1e75-4ac3-baaf-f8548c31b25e/hooks","status":201,"execution":87.066399}

Create Auth Transaction

Now that we've registered our webhook with the Fidel API, we can start creating test transactions, and we should see them coming in the Netlify Function logs. We're using the test transactions endpoint in the Fidel API, and this only works in test mode. If your account is live, you'll need to switch it back to test mode to follow along with this tutorial. Don't forget to replace your locationId and cardId in the request payload.

curl -X POST \
  https://api.fidel.uk/v1/transactions/test \
  -H 'content-type: application/json' \
  -H 'fidel-key: sk_test_50ea90b6-2a3b-4a56-814d-1bc592ba4d63' \
  -d '{
    "amount": 12.34,
    "cardId": "bb9b4a67-203c-4eae-8b09-070e819629cc",
    "locationId": "fe77e7f5-350b-4c34-be68-3e16e7c95d66"
  }'

After you run this command, you can see the webhook logs in the Netlify Function log. Mine looked a bit like this after I received the event on my webhook:

1:51:56 PM: 2020-07-24T12:51:56.589Z    7989b0a6-f0ce-4985-a45f-7e22ec0ff6c6    INFO    {"auth":true,"currency":"GBP","id":"4b549d95-1540-4332-891a-dd2c7603b090","amount":12.34,"wallet":null,"created":"2020-07-24T12:51:55.918Z","accountId":"36081095-2782-4669-8a07-857bbaaeb89b","cleared":false,"updated":"2020-07-24T12:51:55.918Z","programId":"f2c9719a-6433-4ef4-8401-19d7ebf60ab9","datetime":"2020-07-24T13:51:55","card":{"id":"14bda5c9-d5d9-40ef-87e3-158c2f5f2f8d","firstNumbers":"444400","lastNumbers":"4001","scheme":"visa"},"location":{"address":"Titulescu Nr. 16","city":"Bristol","countryCode":"GBR","id":"793f5298-3715-43ef-b89d-1b1cedddd716","geolocation":null,"postcode":"BS16UZ","timezone":"Europe/London","metadata":null},"brand":{"id":"9cd32c61-43ca-4bb7-8aca-0cf491112c28","name":"Avocado","logoURL":"https://developeravocados.net/img/avatar-icon.png","metadata":null},"identifiers":{"MID":"TEST_MID_a7d6bc8f-7837-4f3b-aa43-8c51478ce189","mastercardTransactionSequenceNumber":null,"mastercardRefNumber":null,"amexApprovalCode":null,"visaAuthCode":null}}
1:51:56 PM: Duration: 1.36 ms   Memory Usage: 64 MB 

What's Next?

Hopefully, you've followed me along this journey and we've managed to receive webhook events on a Netlify Function using JavaScript. If you've found them interesting, there are a lot more things you can do with them. For example, you could save that transaction event to a FaunaDB.

If the Fidel API made you curious, and you want to keep exploring, keep in mind all the things I did with cURL today are available either via the Fidel Dashboard or our API. You can check out the API Reference and use your favourite HTTP client to play with it.