How to handle Stripe webhooks with Edge Functions
This guide demonstrates how to securely handle Stripe webhooks using Azion Edge Functions. You’ll learn to process payment events in real-time, verify webhook signatures, and build a robust payment processing system at the edge.
Requirements
Before you begin, ensure you have:
- An Azion account
- A Stripe account with API access
- Azion CLI installed and configured
- Node.js version 18 or higher
- pnpm package manager installed
- Basic understanding of JavaScript, Edge Functions, and Stripe webhooks
- Stripe API keys (both secret key and webhook signing secret)
Getting started
Step 1: Set up your development environment
- Clone the Stripe webhooks example repository:
git clone https://github.com/egermano/edge-functions-examples.gitcd edge-functions-examples/packages/stripe-webhooks
- Install the project dependencies:
pnpm install
- Review the project structure to understand the implementation:
ls -la
You should see the main files including the Edge Function implementation, webhook handlers, and configuration files.
Step 2: Configure environment variables
- Create a
.env
file based on the example:
cp .env.example .env
- Edit the
.env
file to include your Stripe credentials:
# Stripe ConfigurationSTRIPE_SECRET_KEY=sk_test_your_stripe_secret_keySTRIPE_WEBHOOK_SECRET=whsec_your_webhook_signing_secret
- Get your credentials from your Stripe Dashboard:
- Secret Key: Available in API Keys section
- Webhook Secret: Created when setting up webhook endpoints
Step 3: Configure Stripe webhook endpoint
- In your Stripe Dashboard, go to Developers > Webhooks
- Click Add endpoint
- Set the endpoint URL to your future Azion domain (you’ll update this after deployment)
- Select the events you want to listen for:
payment_intent.succeeded
payment_method.attached
charge.succeeded
invoice.payment_succeeded
- Any other events relevant to your application
Step 4: Build the project
Compile your Edge Function for deployment:
pnpm build
This command builds your Edge Function with TypeScript support and prepares it for deployment.
Step 5: Test locally
Before deploying, test your webhook handler locally:
pnpm dev
This starts a local development server where you can test webhook processing.
Understanding webhook verification
Signature verification
Stripe webhooks include a signature header that you must verify to ensure the webhook came from Stripe:
import crypto from 'crypto';
function verifyStripeSignature(payload, signature, secret) { const elements = signature.split(','); const signatureHash = elements.find(element => element.startsWith('v1='));
if (!signatureHash) { throw new Error('Invalid signature format'); }
const expectedHash = crypto .createHmac('sha256', secret) .update(payload) .digest('hex');
const actualHash = signatureHash.split('=')[1];
if (expectedHash !== actualHash) { throw new Error('Invalid webhook signature'); }
return true;}
Webhook event handling
The Edge Function processes different Stripe events:
async function handleWebhookEvent(event) { switch (event.type) { case 'payment_intent.succeeded': await handlePaymentSuccess(event.data.object); break; case 'payment_intent.payment_failed': await handlePaymentFailure(event.data.object); break; case 'charge.succeeded': await handleChargeSuccess(event.data.object); break; case 'invoice.payment_succeeded': await handleInvoicePayment(event.data.object); break; default: console.log(`Unhandled event type: ${event.type}`); }}
Deploying to Azion
Step 1: Authenticate with Azion
- Log in to your Azion account via CLI:
azion login
- Follow the authentication prompts to connect your CLI with your Azion account.
Step 2: Create secrets for Stripe credentials
For security, store your Stripe credentials as secrets:
azion create secret STRIPE_SECRET_KEYazion create secret STRIPE_WEBHOOK_SECRET
When prompted, enter your respective Stripe credentials. This ensures your sensitive data is encrypted and secure.
Step 3: Deploy the Edge Function
Deploy your webhook handler to Azion’s edge network:
azion deploy
The deployment process will:
- Upload your Edge Function code
- Configure the edge application
- Set up the necessary routing rules
- Configure environment variables and secrets
- Provide you with a unique domain
Step 4: Update Stripe webhook configuration
- After deployment, you’ll receive a domain like
https://xxxxxxx.map.azionedge.net
- Go to your Stripe Dashboard > Developers > Webhooks
- Edit your webhook endpoint
- Update the URL to
https://xxxxxxx.map.azionedge.net/webhook
- Save the changes
Step 5: Test webhook delivery
- Trigger test events in Stripe Dashboard
- Monitor webhook delivery and responses
- Check Edge Function logs for processing confirmation
Supported webhook events
Payment events
- payment_intent.succeeded: Payment completed successfully
- payment_intent.payment_failed: Payment attempt failed
- payment_intent.canceled: Payment was canceled
Charge events
- charge.succeeded: Charge completed successfully
- charge.failed: Charge attempt failed
- charge.refunded: Charge was refunded
Invoice events
- invoice.payment_succeeded: Invoice payment completed
- invoice.payment_failed: Invoice payment failed
- invoice.created: New invoice created
Customer events
- customer.created: New customer registered
- customer.updated: Customer information updated
- customer.deleted: Customer account deleted
Implementation examples
Payment success handler
async function handlePaymentSuccess(paymentIntent) { const { id, amount, currency, customer } = paymentIntent;
// Update order status in your database await updateOrderStatus(paymentIntent.metadata.order_id, 'paid');
// Send confirmation email await sendPaymentConfirmation(customer, amount, currency);
// Trigger fulfillment process await triggerFulfillment(paymentIntent.metadata.order_id);
console.log(`Payment succeeded: ${id} for ${amount} ${currency}`);}
Subscription handling
async function handleSubscriptionEvent(subscription) { const { id, customer, status, current_period_end } = subscription;
switch (status) { case 'active': await activateSubscription(customer, id); break; case 'canceled': await cancelSubscription(customer, id); break; case 'past_due': await handlePastDueSubscription(customer, id); break; }
// Update customer record await updateCustomerSubscription(customer, { subscription_id: id, status, next_billing_date: current_period_end });}
Refund processing
async function handleRefund(charge) { const { id, amount, currency, refunded } = charge;
if (refunded) { // Process refund in your system await processRefund({ charge_id: id, amount, currency, order_id: charge.metadata.order_id });
// Notify customer await sendRefundNotification(charge.customer, amount, currency);
console.log(`Refund processed: ${id} for ${amount} ${currency}`); }}
Testing your webhook handler
Step 1: Use Stripe CLI for local testing
- Install Stripe CLI
- Forward webhooks to your local development server:
stripe listen --forward-to localhost:3000/webhook
- Trigger test events:
stripe trigger payment_intent.succeededstripe trigger charge.succeededstripe trigger invoice.payment_succeeded
Step 2: Test in production
- Use Stripe Dashboard to send test webhooks
- Monitor webhook delivery and retry attempts
- Check response codes and processing times
- Verify event handling accuracy
Step 3: Error handling testing
- Test with invalid signatures
- Test with malformed payloads
- Test timeout scenarios
- Verify retry mechanisms
Security best practices
Webhook verification
- Always verify signatures: Never process unverified webhooks
- Use HTTPS: Ensure all webhook endpoints use HTTPS
- Validate payload: Check event structure and required fields
- Implement replay protection: Track processed event IDs
Secret management
- Use Azion secrets: Store credentials securely
- Rotate secrets regularly: Update webhook secrets periodically
- Limit access: Restrict secret access to necessary functions
- Monitor usage: Track secret access and usage patterns
Error handling
async function processWebhook(request) { try { const payload = await request.text(); const signature = request.headers.get('stripe-signature');
// Verify webhook signature verifyStripeSignature(payload, signature, STRIPE_WEBHOOK_SECRET);
// Parse event const event = JSON.parse(payload);
// Process event await handleWebhookEvent(event);
return new Response('OK', { status: 200 }); } catch (error) { console.error('Webhook error:', error); return new Response('Error', { status: 400 }); }}
Monitoring and logging
Event logging
function logWebhookEvent(event, status, processingTime) { console.log({ event_id: event.id, event_type: event.type, status, processing_time: processingTime, timestamp: new Date().toISOString() });}
Performance monitoring
- Response times: Monitor webhook processing speed
- Success rates: Track successful vs failed processing
- Error patterns: Identify common failure scenarios
- Retry frequency: Monitor webhook retry attempts
Troubleshooting
Common issues and solutions
- Signature verification failures: Check webhook secret configuration
- Timeout errors: Optimize processing logic for speed
- Duplicate processing: Implement idempotency checks
- Missing events: Verify webhook endpoint configuration
Debugging tips
- Enable detailed logging: Log all webhook events and processing steps
- Use Stripe Dashboard: Monitor webhook delivery attempts
- Test locally: Use Stripe CLI for local debugging
- Check signatures: Verify webhook signature calculation
Advanced features
Idempotency handling
const processedEvents = new Set();
async function processWebhookWithIdempotency(event) { if (processedEvents.has(event.id)) { console.log(`Event ${event.id} already processed`); return; }
await handleWebhookEvent(event); processedEvents.add(event.id);}
Batch processing
async function processBatchEvents(events) { const batchSize = 10; for (let i = 0; i < events.length; i += batchSize) { const batch = events.slice(i, i + batchSize); await Promise.all(batch.map(event => handleWebhookEvent(event))); }}
Next steps
- Implement comprehensive event logging and monitoring
- Add webhook replay functionality for failed events
- Integrate with your existing payment processing system
- Implement advanced fraud detection mechanisms
- Scale webhook processing for high-volume scenarios