6 min read

What Is Webhook Events in Stripe

Webhook events are how Stripe notifies your application when something happens in your account—a payment succeeds, a subscription renews, a customer is created. Without webhooks, you'd poll Stripe's API every few seconds to check for updates, which is inefficient and will get you rate-limited. Webhooks are the backbone of any production Stripe integration.

Understanding Webhook Events

A webhook event is a JSON payload that Stripe sends to your application in real time when an action occurs. Each event has a type, ID, and a data object containing the resource that triggered it.

See what a webhook event looks like

When Stripe fires an event, it sends an HTTP POST to your endpoint with a JSON payload. Each event has a type (like payment_intent.succeeded), a created timestamp, and a data object containing the actual resource (charge, customer, etc.).

javascript
// This is what Stripe sends to your webhook endpoint
const event = {
  id: 'evt_1234567890',
  object: 'event',
  api_version: '2023-10-16',
  created: 1234567890,
  type: 'payment_intent.succeeded',
  data: {
    object: {
      id: 'pi_1234567890',
      amount: 5000,
      currency: 'usd',
      customer: 'cus_1234567890',
      status: 'succeeded'
    }
  }
};
Example webhook event payload from Stripe

Retrieve a specific event from the Events API

You can query past events using the Stripe API. This is useful for debugging or replaying events that were missed. Use the stripe.events.retrieve() method with an event ID.

javascript
const stripe = require('stripe')('sk_test_...');

const event = await stripe.events.retrieve('evt_1234567890');
console.log(`Event type: ${event.type}`);
console.log(`Created: ${event.created}`);
console.log(`Data:`, event.data.object);
Retrieve and inspect a specific event from Stripe's API
Tip: Every event has a unique id (starting with evt_). Store these IDs in your database to deduplicate and prevent processing the same event twice.

Creating and Registering Webhook Endpoints

A webhook endpoint is a URL on your server where Stripe sends events. You can create and manage endpoints programmatically via the API or manually in the Stripe Dashboard under Developers > Webhooks.

Create a webhook endpoint via the Stripe API

Use stripe.webhookEndpoints.create() to register a new endpoint and specify which events you want to receive. You can set up specific event types like charge.succeeded or customer.created.

javascript
const stripe = require('stripe')('sk_test_...');

const endpoint = await stripe.webhookEndpoints.create({
  url: 'https://example.com/webhook/stripe',
  enabled_events: [
    'charge.succeeded',
    'charge.failed',
    'customer.created',
    'invoice.payment_succeeded'
  ]
});

console.log(`Webhook created: ${endpoint.id}`);
console.log(`Secret: ${endpoint.secret}`); // Store this securely
Create a webhook endpoint and receive the signing secret

Set up your endpoint handler in Node.js

Create an Express route that receives POST requests from Stripe. Verify the webhook signature to confirm the request came from Stripe, then parse and process the event.

javascript
const express = require('express');
const stripe = require('stripe')('sk_test_...');

const app = express();
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

// Use raw body for signature verification
app.post('/webhook/stripe', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  
  try {
    const event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
    
    // Handle the event
    if (event.type === 'charge.succeeded') {
      const charge = event.data.object;
      console.log(`Charge ${charge.id} succeeded for $${charge.amount / 100}`);
    }
    
    res.json({received: true});
  } catch (err) {
    console.error(`Webhook error: ${err.message}`);
    res.status(400).send(`Webhook Error: ${err.message}`);
  }
});

app.listen(3000, () => console.log('Webhook server running'));
Receive and verify Stripe webhooks in Express

List and manage your endpoints

Query all webhook endpoints in your Stripe account using stripe.webhookEndpoints.list(). This is helpful for auditing which endpoints are active and what events they're listening for.

javascript
const stripe = require('stripe')('sk_test_...');

const endpoints = await stripe.webhookEndpoints.list();

for (const endpoint of endpoints.data) {
  console.log(`URL: ${endpoint.url}`);
  console.log(`Status: ${endpoint.status}`);
  console.log(`Events: ${endpoint.enabled_events.join(', ')}`);
}
Audit all webhook endpoints in your account
Watch out: Always verify the webhook signature using stripe.webhooks.constructEvent(). Never trust the event without verification—anyone can POST to your endpoint.

Processing Specific Webhook Events

Different events require different handling. A charge.succeeded event means you should mark an order as paid. A customer.subscription.deleted event means you should downgrade or revoke access.

Handle payment success and failure events

Listen for charge.succeeded and charge.failed to update your order status. The charge object contains the amount, currency, and customer details. Update your database to reflect the outcome.

javascript
app.post('/webhook/stripe', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  const event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
  
  switch(event.type) {
    case 'charge.succeeded':
      const charge = event.data.object;
      await db.orders.update({stripe_charge_id: charge.id}, {status: 'paid'});
      break;
      
    case 'charge.failed':
      const failedCharge = event.data.object;
      console.log(`Payment failed: ${failedCharge.failure_message}`);
      await db.orders.update({stripe_charge_id: failedCharge.id}, {status: 'failed'});
      break;
  }
  
  res.json({received: true});
});
Handle payment outcome events in your database

Handle subscription events

Listen for customer.subscription.updated and customer.subscription.deleted to sync subscription status. This ensures your user's access level stays in sync with Stripe whenever they upgrade, downgrade, or cancel.

javascript
case 'customer.subscription.updated':
  const subscription = event.data.object;
  await db.subscriptions.update(
    {stripe_subscription_id: subscription.id},
    {
      status: subscription.status,
      current_period_end: new Date(subscription.current_period_end * 1000)
    }
  );
  break;
  
case 'customer.subscription.deleted':
  const deletedSub = event.data.object;
  await db.users.update(
    {stripe_customer_id: deletedSub.customer},
    {subscription_status: 'cancelled'}
  );
  break;
Sync subscription lifecycle events with your database
Tip: Test your webhook handler using Stripe's Webhook Testing feature in Developers > Webhooks. You can manually trigger test events without processing real payments.

Common Pitfalls

  • Not verifying webhook signatures — stripe.webhooks.constructEvent() will throw if the signature is invalid, preventing spoofed requests from reaching your logic
  • Using parsed JSON instead of raw body for signature verification — Stripe signs the raw request body, so you must pass the raw bytes to constructEvent(), not a parsed JSON string
  • Processing the same event multiple times — Stripe retries events if your endpoint doesn't respond with a 2xx status. Always deduplicate on event.id before updating your database
  • Not returning a 2xx status code quickly — Stripe expects a response within 5 seconds. Do async work in a background queue, not in the webhook handler itself

Wrapping Up

Webhook events are Stripe's way of keeping your application in sync with payments in real time. By setting up endpoints, verifying signatures, and handling specific event types, you've built a reliable payment system that responds instantly to customer actions. If you want to track this automatically across tools, Product Analyst can help.

Track these metrics automatically

Product Analyst connects to your stack and surfaces the insights that matter.

Try Product Analyst — Free