Working with Stripe Webhooks & Firebase Cloud Functions

What is Stripe?

Stripe is a modern payments platform that allows you to easily accept payment on your web services. This tutorial is aimed at people who either already have Stripe integrated or are in the process of integrating Stripe and are curious about using the webhooks.

We’re going to look at setting up Stripe Webhooks and Firebase Cloud Functions, saving our Stripe events into the Firebase Realtime Database with a quick example of how you could take things further and trigger Firebase Cloud Function database triggers.

Why use webhooks?

Stripe webhooks can be used for many different reasons. You could use them for populating real-time reports/dashboards, monitoring, updating customer information in your database, alerting users on declined subscription renewals and a whole lot more.

What is Firebase?

Firebase is part of Google Cloud Platform and gives you access to several useful tools and services such as hosting, databases, cloud functions, analytics, storage and more.

In this tutorial, we’re going to use Firebase Functions and Firebase Realtime Database, but check out the Firebase website to see what else they have to offer.

The Free plan on Firebase gives you access to 125K Cloud Function invocations per month and 1GB of storage in the Realtime Database which is great — and even if you need more than that, the prices are still incredible.

Let’s get started

To get started, we first need to head over to our Firebase console and create a new project. I’m going to name mine Stripe Webhooks Tutorial, but you can name yours whatever you like.

Create a new Firebase project

Now lets head over to our terminal and create a new directory for our project.

$ mkdir stripe-webhooks
$ cd stripe-webhooks

We then need to install the firebase-tools package globally using NPM.

$ npm install -g firebase-tools

Now we have firebase-tools installed, we should login to our firebase account and initiate a new project in our project directory. You will get a choice of which services you would like to use — you only need to select ‘Functions’ here for now.

$ firebase login
$ firebase init
Set up your project with firebase init

Let’s move into the functions folder (created with firebase init) and make sure we have the latest version of firebase-functions and firebase-admin installed. firebase-functions is used for running the actual cloud functions and firebase-admin is used to access the Realtime Database.

$ cd functions/
$ npm install firebase-functions@latest firebase-admin@latest --save
A blank Firebase project has been created

Let’s create a new function called events to handle our endpoint. We’ll start off simple with a basic function that returns a string to let us generate the URL we need to supply our Stripe account with.

const functions = require('firebase-functions');
exports.events = functions.https.onRequest((request, response) => {
resoinse.send("Endpoint for Stripe Webhooks!");
});

We can then deploy our function and get our endpoint (Function URL in output in screenshot).

firebase deploy --only functions
Deploy to get the URL to supply to Stripe

Now we have our Cloud Function URL, we can head over to the webhooks section of the Stripe dashboard. We then want to + Add Endpoint, and enter our URL into the URL field. You can select the types of events to be sent, but for now we will just stick to all event types.

Once you create the endpoint, take note of your ‘Signing secret’ — we’ll need that to verify the request send to our function.

Set up your webhook endpoint in the Stripe dashboard

While we’re in our Stripe Dashboard, let’s head over to the API Keys section to generate and take not of the API key we’re going to use.

You should create a restricted API key and only assign permissions you’re going to need in your firebase project. For example, if you’re only going to read customer objects, you can specify only Customers when creating the key.

Create a restricted key with limited permissions

Now we have our signing secret and our API key, let’s add them to our Firebase project as environment variables so we don’t need to check them in to any source control.

$ firebase functions:config:set \
keys.webhooks="your_restricted_key" \
keys.signing="your_signing_key"
Add your keys as Firebase environment variables

That’s our setup complete — We’re now ready to write some code! I’m going to be using examples from Firebase and Stripe, so if there’s anything you would like to dive deeper into, you can use the following links:

To start with, we’re going to need the Stripe NPM package, so let’s go ahead and install that:

$ npm install --save stripe

We added our API keys to our Firebase config, so we can access them using functions.config() (For example: functions.config().keys.webhooks will return our keys.webhooks string we added).

We will then require the Stripe package in our functions index.js. We will also bring in our Signing key to our application (endpointSecret).

const functions = require(‘firebase-functions’);
const stripe = require(‘stripe’)(functions.config().keys.webhooks);
const endpointSecret = functions.config().keys.signing;
exports.events = functions.https.onRequest((request, response) => {
response.send(“Endpoint for Stripe Webhooks!”);
});

Note: Stripe marks a webhook as successful only when your function returns a success (2xx) response. If it receives anything else, such as a 400 or 500, then it marks it as failed, and will try again.

We can use our signing key to verify that a request has actually come from Stripe, and not an unauthorized attacker. The stripe package has a method (stripe.webhooks.constructEvent) which we can use to verify the request. We can also use a Try Catch to return an error if the request fails verification.

// Get the signature from the request header
let sig = request.headers["stripe-signature"];
// Verify the request against our endpointSecret
let event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret);

Note: We need to use the original request body otherwise the verification will fail, so we must use Firebase Function’s request.rawBody, instead of the usual request.body.

As mentioned, we can wrap this in a Try Catch to catch any failed requests.

let sig = request.headers["stripe-signature"];
try {
let event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret);
} catch (err) {
return response.status(400).end();
}
We’ve set up Stripe and now verify the requests

Now we have our valid events, let’s save them to our Firebase Realtime Database.

We can do this by using the firebase-admin database methods. We’re going to be using the .push() method to create a new entry in our database.

const admin = require('firebase-admin');
admin.initializeApp();
...
return admin.database().ref('/events').push(event)
.then((snapshot) => {
return response.json({ received: true, ref: snapshot.ref.toString() });
})
.catch((err) => {
console.error(err);
return response.status(500).end();
});

Let’s break this code down a bit.

  • Ref is the path in the database we would like to save our new entry to. This can be whatever you like — i’ve chosen so save my events in the /events ref.
  • Push(event)- event is the variable we’re saving the response from the constructEvent method we called. This is an object with all of our event info
  • Response.json — We respond with a valid json object — this tells Stripe that the webhook event was received and we’re happy we’ve processed it, so mark it as complete.
  • Catch — In case something goes wrong while we’re saving the event to the database, we return an error 500 to tell Stripe that something went wrong. Stripe will then retry sending the event. There are ways you could incorporate this into the Try Catch we have, although I like having the differentiation of errors.

Now we should deploy our function again, then we can test it out.

$ firebase deploy --only functions

Let’s head back over to the Stripe Dashboard Webhooks area, select our endpoint where we can now ‘Send test webhook’.

Select an event type and hit ‘Send test webhook’ — all going well, we get a successful response, and our event is now saved in our database!

Send a test event and view it in our database

That’s it in terms of saving. Now you have endless possibilities of cool things to do with your data. For further reading, you could explore the different triggers Cloud Functions can use. You can run another function whenever anything is added to your database. The example below would run any time a new event is saved to our database. We could now check the event type, and if it’s a successful charge, update our Realtime database to increase our daily revenue on our live dashboard…

You can read more about database events here: https://firebase.google.com/docs/functions/database-events

Example database trigger function

I hope this was useful. Please leave any feedback or questions - I’m always learning and really appreciate any comments you may have.

You can find the completed code over on Github — https://github.com/GaryH21/Stripe-Webhooks-Tutorial

Happy building!