Payment Webhook Configuration
Complete guide to configure payment webhooks for Stripe, Creem, and PayPal
Payment Webhook Configuration Guide
This guide explains how to configure payment webhooks for your application to receive real-time payment notifications.
Overview
The application has a unified webhook endpoint that supports multiple payment providers:
https://your-domain.com/api/payment/notify/[provider]Supported Providers
- Stripe:
https://your-domain.com/api/payment/notify/stripe - Creem:
https://your-domain.com/api/payment/notify/creem - PayPal:
https://your-domain.com/api/payment/notify/paypal
Environment Variables
Configure the following variables in your .env file:
Stripe Configuration
STRIPE_SECRET_KEY="sk_test_xxx..."
STRIPE_PUBLISHABLE_KEY="pk_test_xxx..."
STRIPE_SIGNING_SECRET="whsec_xxx..." # Webhook signing secret (required!)
STRIPE_ENABLED="true"Creem Configuration
CREEM_API_KEY="xxx..."
CREEM_ENVIRONMENT="sandbox" # or "production"
CREEM_SIGNING_SECRET="xxx..." # Webhook signing secret
CREEM_ENABLED="true"PayPal Configuration
PAYPAL_CLIENT_ID="xxx..."
PAYPAL_CLIENT_SECRET="xxx..."
PAYPAL_ENVIRONMENT="sandbox" # or "production"
PAYPAL_ENABLED="true"Default Provider
DEFAULT_PAYMENT_PROVIDER="stripe" # stripe, creem, or paypalStripe Webhook Setup
Step 1: Access Stripe Dashboard
Visit Stripe Webhooks Dashboard
Step 2: Add Endpoint
- Click "Add endpoint"
- Enter Endpoint URL:
https://your-domain.com/api/payment/notify/stripe - Select the following events:
Required Events:
- ✅
checkout.session.completed- Checkout completed - ✅
invoice.payment_succeeded- Payment succeeded (subscription renewal) - ✅
invoice.payment_failed- Payment failed - ✅
customer.subscription.updated- Subscription updated - ✅
customer.subscription.deleted- Subscription canceled
Step 3: Get Signing Secret
- After creating the endpoint, click "Reveal" to show the Signing Secret
- Copy the
whsec_xxx...format key - Add it to your
.envfile asSTRIPE_SIGNING_SECRET
Step 4: Test Webhook
Using Stripe CLI to test locally:
# Install Stripe CLI (macOS)
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Forward webhook to local
stripe listen --forward-to localhost:3000/api/payment/notify/stripe
# Trigger test event (in another terminal)
stripe trigger checkout.session.completed
stripe trigger invoice.payment_succeeded
stripe trigger customer.subscription.updatedWebhook Events
The application handles the following webhook events:
| Event Type | Stripe Event | Description |
|---|---|---|
CHECKOUT_SUCCESS | checkout.session.completed | Handles first payment/subscription creation |
PAYMENT_SUCCESS | invoice.payment_succeeded | Handles subscription renewal payment |
PAYMENT_FAILED | invoice.payment_failed | Handles payment failure |
SUBSCRIBE_UPDATED | customer.subscription.updated | Updates subscription information |
SUBSCRIBE_CANCELED | customer.subscription.deleted | Handles subscription cancellation |
Webhook Flow
POST /api/payment/notify/stripe
↓
Verify webhook signature (Signing Secret)
↓
Parse event type
↓
Handle based on event type:
┌─ CHECKOUT_SUCCESS
│ ↓ Find order
│ ↓ Update order status to SUCCESS
│ ↓ Create credit transaction
│ ↓ Create subscription record (if subscription)
│
┌─ PAYMENT_SUCCESS (subscription renewal)
│ ↓ Find subscription
│ ↓ Update subscription period
│ ↓ Create renewal order
│ ↓ Add renewal credits
│
┌─ SUBSCRIBE_UPDATED
│ ↓ Find subscription
│ ↓ Update subscription info
│
└─ SUBSCRIBE_CANCELED
↓ Find subscription
↓ Update subscription status to CANCELEDSecurity Verification
The webhook endpoint uses signature verification to ensure security:
// Stripe example
async getPaymentEvent({ req }: { req: Request }): Promise<PaymentEvent> {
const rawBody = await req.text();
const signature = req.headers.get('stripe-signature') as string;
if (!rawBody || !signature) {
throw new Error('Invalid webhook request');
}
if (!this.configs.signingSecret) {
throw new Error('Signing Secret not configured');
}
// Verify signature
const event = this.client.webhooks.constructEvent(
rawBody,
signature,
this.configs.signingSecret
);
// Process event...
}Creating Payments with Metadata
When creating payments, you must set the order_no in metadata for webhooks to work correctly:
const paymentService = await getPaymentService();
const session = await paymentService.createPayment({
order: {
type: PaymentType.ONE_TIME,
price: {
amount: 1000, // in cents (10.00 USD)
currency: 'usd',
},
description: 'Purchase credits',
customer: {
email: user.email,
name: user.name,
},
successUrl: `${process.env.NEXT_PUBLIC_APP_URL}/payment/success`,
cancelUrl: `${process.env.NEXT_PUBLIC_APP_URL}/payment/cancel`,
// Important: Set metadata for webhook processing
metadata: {
order_no: orderNo, // Order number (required)
user_id: userId, // User ID
product_id: productId, // Product ID
},
},
});Monitoring and Debugging
View Webhook Logs
# Production logs
tail -f /var/log/app/production.log | grep "payment notify"
# Or check Stripe Dashboard
# Webhooks → your endpoint → view logsCommon Issues
Issue 1: Webhook signature verification failed
Error: No signatures found matching the expected signature for payload
Solution: Check if STRIPE_SIGNING_SECRET is correctly configuredIssue 2: Order not found
Error: order not found
Cause: order_no in metadata doesn't exist
Check: Verify metadata.order_no is set when creating paymentIssue 3: Webhook timeout
Cause: Processing logic takes too long (Stripe requires response within 5 seconds)
Suggestion: Use background job queue for time-consuming operationsConfiguration Checklist
Before deployment, verify:
- ✅ Configured
SIGNING_SECRETin.envfile - ✅ Configured correct webhook URL in payment provider dashboard
- ✅ Webhook URL is accessible from public internet (production)
- ✅ Selected correct webhook event types
- ✅ Tested at least one payment flow
- ✅ Checked webhook logs for errors
Testing in Production
- Go to Stripe Dashboard → Webhooks → your endpoint
- Click "Send test webhook"
- Select event type and send test
Troubleshooting
If webhooks are not working:
-
Check endpoint accessibility: Use curl to test
curl -X POST https://your-domain.com/api/payment/notify/stripe -
Verify signing secret: Check it matches between Stripe and
.env -
Check logs: Look for error messages in application logs
-
Test locally: Use Stripe CLI to forward webhooks to local development
Related Files
- Webhook handler:
src/app/api/payment/notify/[provider]/route.ts - Payment service:
src/shared/services/payment.ts - Stripe provider:
src/extensions/payment/stripe.ts - Payment types:
src/extensions/payment/index.ts