He
HeliumTS
Note:

HeliumTS is under pre-beta and active development. Expect bugs and breaking changes. If you find any issues, please report them in our GitHub

A stable release is planned for early December 2025.

Stripe Webhooks

This guide shows how to handle Stripe webhooks with HeliumTS.

Setup

First, install the Stripe SDK:

%npm install stripe

Add your Stripe keys to your environment variables:

%STRIPE_SECRET_KEY=sk_test_...
%STRIPE_WEBHOOK_SECRET=whsec_...

Webhook Handler

Handle Stripe webhooks to process events like successful payments:

1import { defineHTTPRequest } from "heliumts/server";
2import Stripe from "stripe";
3
4const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
5 apiVersion: "2024-11-20.acacia",
6});
7
8export const stripeWebhook = defineHTTPRequest("POST", "/webhooks/stripe", async (req, ctx) => {
9 const body = await req.text();
10 const signature = req.headers["stripe-signature"] as string;
11
12 if (!signature) {
13 return new Response(JSON.stringify({ error: "No signature" }), {
14 status: 400,
15 headers: { "Content-Type": "application/json" },
16 });
17 }
18
19 try {
20 const event = stripe.webhooks.constructEvent(
21 body,
22 signature,
23 process.env.STRIPE_WEBHOOK_SECRET!
24 );
25
26 // Handle different event types
27 switch (event.type) {
28 case "payment_intent.succeeded": {
29 const paymentIntent = event.data.object as Stripe.PaymentIntent;
30 console.log("Payment succeeded:", paymentIntent.id);
31 // Update your database, send confirmation email, etc.
32 break;
33 }
34
35 case "checkout.session.completed": {
36 const session = event.data.object as Stripe.Checkout.Session;
37 console.log("Checkout completed:", session.id);
38 // Fulfill the order
39 break;
40 }
41
42 case "customer.subscription.created": {
43 const subscription = event.data.object as Stripe.Subscription;
44 console.log("Subscription created:", subscription.id);
45 // Update user's subscription status
46 break;
47 }
48
49 case "customer.subscription.deleted": {
50 const subscription = event.data.object as Stripe.Subscription;
51 console.log("Subscription cancelled:", subscription.id);
52 // Revoke access
53 break;
54 }
55
56 case "invoice.payment_failed": {
57 const invoice = event.data.object as Stripe.Invoice;
58 console.log("Payment failed:", invoice.id);
59 // Notify customer
60 break;
61 }
62
63 default:
64 console.log(`Unhandled event type: ${event.type}`);
65 }
66
67 return new Response(JSON.stringify({ received: true }), {
68 status: 200,
69 headers: { "Content-Type": "application/json" },
70 });
71 } catch (error) {
72 console.error("Webhook error:", error);
73
74 return new Response(
75 JSON.stringify({ error: "Webhook signature verification failed" }),
76 {
77 status: 400,
78 headers: { "Content-Type": "application/json" },
79 }
80 );
81 }
82});

Testing Webhooks Locally

Use Stripe CLI to forward webhooks to your local server:

%# Install Stripe CLI
%brew install stripe/stripe-cli/stripe
%
%# Login to Stripe
%stripe login
%
%# Forward webhooks to your local server
%stripe listen --forward-to localhost:3000/webhooks/stripe

The CLI will output your webhook signing secret. Add it to your .env file.

Best Practices

  • Always verify webhook signatures to prevent fraud
  • Use environment variables for API keys and secrets
  • Handle webhook events idempotently (same event may be sent multiple times)
  • Return a 200 response quickly to acknowledge receipt
  • Process webhook events asynchronously if they take time
  • Log all webhook events for debugging
  • Test with Stripe test mode before going live
  • Implement proper error handling and monitoring