Skip to content

Latest commit

 

History

History
261 lines (199 loc) · 7.1 KB

File metadata and controls

261 lines (199 loc) · 7.1 KB

Volley + Stripe Webhook Example

Complete example of testing Stripe webhooks locally using Volley - No ngrok, no tunneling, no exposing your server.

Volley Stripe Node.js

This repository demonstrates how to test Stripe webhooks locally using Volley, a webhook-as-a-service platform. Unlike ngrok, Volley provides permanent webhook URLs and doesn't require tunneling.

🎯 What You'll Learn

  • How to set up Stripe webhooks with Volley
  • How to test webhooks locally without exposing your server
  • How to handle Stripe webhook events in Node.js/Express
  • Best practices for webhook security and verification

🚀 Quick Start

Prerequisites

Installation

  1. Clone this repository:

    git clone https://github.com/volleyhq/volley-stripe-example.git
    cd volley-stripe-example
  2. Install dependencies:

    npm install
  3. Set up environment variables:

    cp .env.example .env
    # Edit .env with your Stripe secret key
  4. Create a Volley source:

    • Go to Volley Dashboard
    • Create a new organization and project
    • Create a new source (e.g., "stripe-webhooks")
    • Copy your ingestion ID (e.g., abc123xyz)
  5. Configure Stripe webhook:

    • Go to Stripe Dashboard
    • Click "Add endpoint"
    • Enter your Volley webhook URL: https://api.volleyhooks.com/hook/YOUR_INGESTION_ID
    • Select events to listen to (e.g., payment_intent.succeeded, customer.created)
    • Click "Add endpoint"
  6. Start your local server:

    npm start
  7. Forward webhooks to localhost:

    volley listen --source YOUR_INGESTION_ID --forward-to http://localhost:3000/webhooks/stripe
  8. Test it:

    • Trigger a test event in Stripe Dashboard
    • Watch it appear in your local server logs!

📁 Project Structure

volley-stripe-example/
├── src/
│   ├── server.js          # Express server setup
│   ├── webhookHandler.js  # Stripe webhook handler
│   └── handlers/          # Event-specific handlers
│       ├── paymentIntent.js
│       ├── customer.js
│       └── invoice.js
├── .env.example           # Environment variables template
├── package.json
└── README.md

💻 Code Example

Basic Webhook Handler

// src/webhookHandler.js
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const router = express.Router();

// Stripe webhook endpoint
router.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    // Verify webhook signature
    event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    console.error('Webhook signature verification failed:', err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  switch (event.type) {
    case 'payment_intent.succeeded':
      await handlePaymentIntentSucceeded(event.data.object);
      break;
    case 'customer.created':
      await handleCustomerCreated(event.data.object);
      break;
    case 'invoice.payment_succeeded':
      await handleInvoicePaymentSucceeded(event.data.object);
      break;
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }

  // Return a response to acknowledge receipt
  res.json({ received: true });
});

module.exports = router;

Event Handlers

// src/handlers/paymentIntent.js
async function handlePaymentIntentSucceeded(paymentIntent) {
  console.log('Payment succeeded:', paymentIntent.id);
  // Your business logic here
  // e.g., update database, send confirmation email, etc.
}

// src/handlers/customer.js
async function handleCustomerCreated(customer) {
  console.log('Customer created:', customer.id);
  // Your business logic here
}

🔐 Security Best Practices

1. Verify Webhook Signatures

Always verify Stripe webhook signatures to ensure requests are from Stripe:

const event = stripe.webhooks.constructEvent(
  req.body,
  sig,
  process.env.STRIPE_WEBHOOK_SECRET
);

2. Use Environment Variables

Never commit secrets to version control:

# .env
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

3. Idempotency

Handle duplicate webhooks gracefully:

// Use Stripe event IDs for idempotency
const eventId = event.id;
if (await isEventProcessed(eventId)) {
  return; // Already processed
}
await markEventAsProcessed(eventId);

🧪 Testing

Manual Testing

  1. Use Stripe CLI to send test webhooks:

    stripe trigger payment_intent.succeeded
  2. Or use Stripe Dashboard:

    • Go to Webhooks → Your endpoint → Send test webhook

Automated Testing

// test/webhook.test.js
const request = require('supertest');
const app = require('../src/server');

describe('Stripe Webhooks', () => {
  it('should handle payment_intent.succeeded', async () => {
    const event = {
      id: 'evt_test',
      type: 'payment_intent.succeeded',
      data: { object: { id: 'pi_test' } }
    };
    
    const response = await request(app)
      .post('/webhooks/stripe')
      .send(JSON.stringify(event))
      .set('stripe-signature', 'test-signature');
    
    expect(response.status).toBe(200);
  });
});

🆚 Why Volley Instead of ngrok?

Feature Volley ngrok
Webhook URLs ✅ Permanent, never change ❌ Change on restart
Tunneling ❌ Not required ✅ Required
Local Server Privacy ✅ Completely private ⚠️ Exposed through tunnel
Built-in Retry ✅ Automatic ❌ No
Monitoring ✅ Full dashboard ❌ Limited
Production Ready ✅ Same URL for dev/prod ❌ Dev tool only

Learn more: Volley vs ngrok

📚 Additional Resources

🤝 Contributing

Found a bug or have a suggestion? Please open an issue or submit a pull request!

📄 License

MIT License - See LICENSE file for details


Built with ❤️ using Volley and Stripe