Stripe Webhooks Tutorial
Learn what Stripe Webhooks are and how to work with them on localhost. We will demonstrate Webhooks using Express and the Stripe CLI.
Table of Contents 📖
- What are Stripe Webhooks?
- Creating a Webhook
- Stripe CLI Setup
- Coding a Webhook Endpoint
- Publishing Events with Stripe CLI
What are Stripe Webhooks?
Stripe will emit events to our server on certain actions such as a payment being processed successfully, a new customer being created, etc. These events contains information about the event such as the its type and data associated with that event type. For example, if the event type is "customer.created" we can retrieve customer information from the event, if the event type is "payment_intent.succeeded" we can retrieve payment intent information from the event, and so on. We can then use the information provided in this event object to do things like update our database, send an email to the user, etc. The endpoint that handles all this is called a webhook.
INFO: A Stripe webhook is an HTTP endpoint that receives events from Stripe.
Creating a Webhook
We can create a webhook in the Stripe dashboard by going to the following URL:
https://dashboard.stripe.com/test/webhooks
When on this page, click "Add an endpoint". After this we can fill in the following information:
- Endpoint URL - The URL for Stripe to contact.
- Description - A description of what this webhook endpoint does.
- Events - List of events for the endpoint to listen to. These events are divided into categories such as Payment Intent, Refund, Customer, etc.
INFO: We can have multiple webhooks and each webhook can listen for multiple events!
Stripe CLI Setup
However, another option we have to work with webhooks, specifically to test them locally, is to use the Stripe CLI. The Stripe CLI helps us build and test a Stripe integration from the terminal. This includes securely testing the webhooks we have created. The Stripe CLI can be installed by following the install directions on the following URL:
https://github.com/stripe/stripe-cli
We can verify the Stripe CLI was installed successfully by checking its version in the terminal.
stripe -v
stripe version 1.18.0
Next, we need to login to Stripe by running the following command:
stripe login
Your pairing code is: michael-jackson-hee-hee
This pairing code verifies your authentication with Stripe.
Press Enter to open the browser or visit https://dashboard.stripe.com/stripecli/confirm_auth?t=asdfasdf234WGGRE23sd
Clicking enter will take us to the Stripe dashboard where we are prompted for access. The prompt will contain the pairing code displayed in the Stripe CLI login. After clicking "allow access" the following will be displayed back in the terminal.
Done! The Stripe CLI is configured for MichaelJackson with account id acct_234534dafasdf
Please note: this key will expire after 90 days, at which point you'll need to re-authenticate.
Now we can start to work with the Stripe CLI. We want it to forward events to our webhook. We can do this with the stripe listen command.
stripe listen --forward-to localhost:6789/my-webhook
Ready! You are using Stripe API Version [2024-04-10].
Your webhook signing secret is whsec_2452345kjhlkjh00adsfkj1234123472 (^C to quit)
Note the webhook secret printed to the console. We will need to use this secret to construct a webhook event.
INFO: Events sent by Stripe are signed to avoid replay attacks. Therefore, the Webhook secret is sensitive and should be kept out of view from the public.
Coding a Webhook Endpoint
Now that we have Stripe set up to send events to localhost:6789/my-webhook, we should create a route to handle these events. These events will come in as a JSON payload in a POST request.
import Stripe from 'stripe';
import express from 'express';
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;
const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET;
const HOST = process.env.HOST;
const PORT = process.env.PORT;
const STRIPE = new Stripe(STRIPE_SECRET_KEY);
const app = express();
app.post('/my-webhook', express.raw({type: 'application/json'}), async (req, res) => {
try {
const sig = req.headers['stripe-signature'];
const event = STRIPE.webhooks.constructEvent(req.body, sig, STRIPE_WEBHOOK_SECRET);
if (event.type === 'customer.created') {
const customer = await STRIPE.customers.retrieve(event.data.object.id);
console.log('Customer retrieved: ', customer);
} else {
console.log('Unhandled event', event.type);
}
return res.sendStatus(200);
} catch (err) {
console.error('Error handling webhook event.', err);
return res.sendStatus(400);
}
});
app.listen(PORT, HOST, () => {
console.log(`Server running on ${HOST}:${PORT}`);
});
- Import Stripe and Express.
- Instantiate some environment variables. The Stripe secret allows us to make API calls to Stripe and the Webhook Secret is the secret generated by Stripe in the terminal.
- Create a Express app and define a route to handle webhook events.
- Get the signature sent from Stripe and use it to construct the webhook event. The signature is used to verify the source of the Webhook request as Stripe. This prevents fake payloads.
- If the event type is "customer.created", we can retrieve the customer information from Stripe and print it to the console.
- If the event type is not "customer.created", just log the event type to the console.
- Send back a 200 response to indicate that the request was successful.
- Log any errors to the console and send back a 400 response.
INFO: Stripe requires the Webhook payload to be a string or Buffer. This is why we use the express.raw middleware before handling the request. This middleware parses request payloads into a Buffer.
Publishing Events with Stripe CLI
Now that we have a webhook endpoint created and Stripe set up to listen to events, we can start publishing events with the stripe trigger command. Specifically, we will trigger the customer.created event.
stripe trigger customer.created
Setting up fixture for: customer
Running fixture for: customer
Trigger succeeded! Check dashboard for event details.
Publihsing this event will cause Stripe to create a customer for us and then fire an event to our webhook endpoint. If we check the logs for our Express application, we can see both the event and the retrieved customer.
Event received {
id: 'evt_1PLRK6Kpg70t05MvvyfnW8sg',
object: 'event',
api_version: '2024-04-10',
created: 1716907702,
data: {
object: {
id: 'cus_QBoyA67bCFami7',
object: 'customer',
address: null,
balance: 0,
created: 1716907702,
currency: null,
default_source: null,
delinquent: false,
description: '(created by Stripe CLI)',
discount: null,
email: null,
invoice_prefix: 'C262C0DB',
invoice_settings: [Object],
livemode: false,
metadata: {},
name: null,
phone: null,
preferred_locales: [],
shipping: null,
tax_exempt: 'none',
test_clock: null
}
},
livemode: false,
pending_webhooks: 2,
request: {
id: 'req_eVNkC1nSVsMMTr',
idempotency_key: '44d2611e-08c4-4047-929d-d6f91e40eab9'
},
type: 'customer.created'
}
INFO: Note how the event contains the customer information inside its data property. The data property contains data associated with the event. For example, the customer.created event will have a customer object as the value.
Customer retrieved: {
id: 'cus_QBnliubrgqi9Zx',
object: 'customer',
address: null,
balance: 0,
created: 1716903248,
currency: null,
default_source: null,
delinquent: false,
description: '(created by Stripe CLI)',
discount: null,
email: null,
invoice_prefix: '6BF6F343',
invoice_settings: {
custom_fields: null,
default_payment_method: null,
footer: null,
rendering_options: null
},
livemode: false,
metadata: {},
name: null,
phone: null,
preferred_locales: [],
shipping: null,
tax_exempt: 'none',
test_clock: null
}
We can also see the response sent from our Express server in the logs for our Stripe CLI.
2024-05-28 14:32:29 --> customer.created [evt_1PLQ8e3245sdfg6tpt7iek]
2024-05-28 14:32:29 <-- [200] POST http://localhost:6789/my-webhook [evt_1PLQ8eKq3o04i9857lkahsdft7iek]
2024-05-28 14:34:08 --> customer.created [evt_1PLQAGKpg70t05Mvo7uuUHiH]
2024-05-28 14:34:09 <-- [200] POST http://localhost:6789/my-webhook [evt_1adkfjgh349it70987aldskfuUHik]
If we check the customers in the Stripe dashboard, we can see the new customers listed as '(created by Stripe CLI)'.