Feat/stripe webhook#1888
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 45ccb3a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
|
||
| export const stripeWebhook = (options: Options): MiddlewareHandler => { | ||
| const { secret, tolerance = 300 } = options | ||
| const stripe = new Stripe(secret, { apiVersion: '2025-02-24.acacia' }) |
There was a problem hiding this comment.
Should apiVersion be an option? What other options does the Stripe constructor accept? Should they be exposed too?
There was a problem hiding this comment.
added apiVersion?: Stripe.LatestApiVersion to Options
| const rawBody = await c.req.raw.clone().text() | ||
| const signature = c.req.header('stripe-signature') | ||
|
|
||
| if (!signature) { | ||
| return c.json({ error: 'Invalid webhook signature' }, 400) | ||
| } |
There was a problem hiding this comment.
Should the body be cloned after the signature is checked?
| declare module 'hono' { | ||
| interface ContextVariableMap { | ||
| stripeEvent: Stripe.Event | ||
| } | ||
| } |
There was a problem hiding this comment.
Generally it's better to leave this up to applications to define
There was a problem hiding this comment.
removed the declare module 'hono' block.
| "name": "@hono/sentry", | ||
| "version": "1.2.2", |
There was a problem hiding this comment.
This needs updating
There was a problem hiding this comment.
deno.json name/version — fixed, was a leftover from copying the sentry package.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@solasamuel @BarryThePenguin Thanks! It's hard to accept this PR. We don't add more middleware easily to reduce the maintenance cost. This Stripe webhook feature is simple code and can be implemented by yourself. It's better to add the instruction for the Stripe webhook on the Examples of our website: https://hono.dev/examples/ |
Summary
Adds
@hono/stripe-webhook— Stripe webhook signature verification middleware for Hono. The middleware reads the raw request body, validates thestripe-signatureheader, and exposes the verifiedStripe.Eventon the request context for downstream handlers.Motivation
Webhook signature verification is required by every Hono app handling Stripe payments. There is no official
@honomiddleware for this today, so every project rolls its own — which is the kind of thing that's easy to get subtly wrong (consuming the body too early, missing the timestamp tolerance, forgetting thatconstructEventdoesn't run on Workers).Usage
ContextVariableMapis augmented, soc.get('stripeEvent')is typed without needing a generic onHono<...>.API
secretstringwhsec_...).tolerancenumber300On verification failure or a missing
stripe-signatureheader, the middleware short-circuits with400 { error: "Invalid webhook signature" }.Notes
c.req.raw.clone().text()so the request stream stays readable for downstream handlers.stripe.webhooks.constructEventAsyncrather than the syncconstructEvent. The sync variant relies on Node'scryptomodule and won't run on Cloudflare Workers or other edge runtimes; the async variant uses Web Crypto and works everywhere. Same runtime targets as@hono/sentry.stripe ^17.0.0is declared as a peer dep so consumers control the SDK version.Testing
vitestsuite insrc/index.test.tsmocksstripeviavi.mock('stripe', ...)and covers:next()and exposes the event on contextstripe-signatureheader → 400toleranceis forwarded toconstructEventAsyncChecklist
yarn test— 6/6)yarn build)