Skip to content

How to Discover API Signatures and Verify Callers Using the Stripe API

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

When you are working with sensitive data like customer payment information or login credentials, you will want to ensure that the data you are receiving from the backend is properly authenticated. API signature verification will check for proper authentication and help ensure that it has not been tampered with.

In this article, we will learn how API signatures work, and look at an example using the Stripe API within Stripe Apps.

Prerequisites: This article will assume that you have a basic working knowledge of the fetch API, JavaScript, Node, and Express. We are also going to be looking at an example using Stripe Apps. I encourage you to create a Stripe account if you are interested in trying out some of the examples on your own.

API signatures and hashing algorithms

When an API request is made, an unique digital signature is created. These signatures use a hashing algorithm which takes the inputted data and converts it to a fixed length string of enciphered data. Popular forms of hashing algorithms include the Secure Hash Algorithm (SHA), and the (Rivest-Shamir-Adleman)(RSA) algorithm.

Here is a basic example of using the HMAC-SHA1 hash algorithm with Node.js.

const crypto = require("crypto");

const text = "This is just demo text";
const key = "x1fjd918dkcn88283sdnc";

// the following code bellow would return this result: ef4dbb240f1f2724a0f909f762b3e539e16f8c3d
crypto.createHmac("sha1", key).update(text).digest("hex");

Third party APIs like PayPal or Stripe have their own verification methods and libraries that use these popular hashing algorithms underneath the hood.

Let's take a look at an example where we will create a fetch call with an API signature using Stripe's fetchStripeSignature and verify the request on the backend using the verifyHeader method.

How to use Stripe's fetchStripeSignature in the frontend

Before we make a fetch call, we will need to first get a Stripe signature. Within Stripe's Extensions SDK, we will need to import the fetchStripeSignature function. This function will create a signature that we will use to send to the backend for verification.

import fetchStripeSignature from "@stripe/ui-extension-sdk/utils";

We are now going to start setting up our fetch request with the POST method.

fetch("url-goes-here", {
  method: "POST",
});

Within the headers object, we are going to invoke the fetchStripeSignature function and add it as a value to the 'Stripe-Signature' key. We are also going to set the content type to JSON.

fetch("url-goes-here", {
  method: "POST",
  headers: {
    "Stripe-Signature": fetchStripeSignature(),
    "Content-Type": "application/json",
  },
});

For the body, we are going to add the user and account IDs because Stripe signatures, by default, are signed with those IDs. We are also going to use the JSON.stringify method to ensure that we are working with a JSON string.

fetch("url-goes-here", {
  method: "POST",
  headers: {
    "Stripe-Signature": fetchStripeSignature(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    user_id: "user-id-goes-here",
    account_id: "account-id-goes-here",
  }),
});

In order to get the user and account IDs, Stripe Apps has a userContext object which comes from the ExtensionContextValue. To learn more, please visit the Stripe Extensions API reference page.

How to verify the request on the backend using the verifyHeader method

Now that we have made our fetch call on the frontend, we now need to verify that data on the backend. We first need to import the Stripe API, and add our Stripe API key and secret. These API keys are used to help authenticate the request and can be found on your Stripe Developer Dashboard. It is a best practice to place your API keys in an .env file like this:

STRIPE_API_KEY=sk_test_...
STRIPE_APP_SECRET=absec_...

Then you can access those keys use process.env.

const stripeAPI = require('stripe')(process.env.STRIPE_API_KEY);
const secretAPIKey = process.env.STRIPE_APP_SECRET;

Then we are going to setup a small Express sever.

const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
const stripe = require("stripe")(process.env.STRIPE_API_KEY);
const secretAPIKey = process.env.STRIPE_APP_SECRET;

app.use(express.json());

app.post("/endpoint-goes-here", (req, res) => {
  // this is where we are going to verify the signature
});

app.listen(port, () => console.log(`This server is running on port: ${port}`));

Within our post, we need to setup the request to get the Stripe signature header.

app.post("/endpoint-goes-here", (req, res) => {
  const stripeSignature = req.headers["stripe-signature"];
});

Next, we need to request the user and account ids.

app.post("/endpoint-goes-here", (req, res) => {
  const stripeSignature = req.headers["stripe-signature"];
  const accountIds = JSON.stringify({
    user_id: req.body["user_id"],
    account_id: req.body["account_id"],
  });
});

We are now going to setup a try catch block and first try to verify the signature for its authenticity. If it is not a valid signature, then we will setup an error message and add a 400 status code in the catch block.

For the verification, we are going to use Stripe's verifyHeader method and pass in the account IDs, Stripe signature, and the Stripe API secret key.

app.post("/endpoint-goes-here", (req, res) => {
  const stripeSignature = req.headers["stripe-signature"];
  const accountIds = JSON.stringify({
    user_id: req.body["user_id"],
    account_id: req.body["account_id"],
  });
  try {
    stripeAPI.webhooks.signature.verifyHeader(
      accountIds,
      stripeSignature,
      secretAPIKey
    );
  } catch (err) {
    res
      .status(400)
      .send(`There was an error validating this signature: ${err.message}`);
  }
});

At the bottom of our post function, we are going to return the JSON response.

app.post("/endpoint-goes-here", (req, res) => {
  const stripeSignature = req.headers["stripe-signature"];
  const accountIds = JSON.stringify({
    user_id: req.body["user_id"],
    account_id: req.body["account_id"],
  });
  try {
    stripeAPI.webhooks.signature.verifyHeader(
      accountIds,
      stripeSignature,
      secretAPIKey
    );
  } catch (err) {
    res
      .status(400)
      .send(`There was an error validating this signature: ${err.message}`);
  }
    // This request is now verified as trusted. Put the rest of your endpoint logic here.
  res.json("response-object-goes-here");
});

Conclusion

In this article, we learned about API signatures and how it uses popular hashing algorithms underneath the hood. We then looked at an example using the Stripe API within the Stripe Apps platform. We first setup a frontend fetch call using the POST method and created a Stripe signature using the fetchStripeSignature function. Then, we verified that signature on the backend using the verifyHeader method.

I hope you enjoyed this article and best of luck on your programming journey.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co