Skip to content

Stripe Webhook

This introduces how to create an API with Hono to receive Stripe Webhook events.

Preparation

Please install the official Stripe SDK at first:

bash
npm install stripe

And put the following values on the .dev.vars file to insert the Stripe API keys:

STRIPE_API_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx

You can learn about the Stripe API keys by the following documents:

How to protect the API for Stripe Webhook events

The API that processes webhook events is publicly accessible. Therefore, a mechanism is needed to protect it from attacks such as malicious third parties spoofing Stripe's webhook event objects and sending requests. In Stripe's case, you can protect the API by issuing a webhook secret and verifying each request.

Learn more: https://docs.stripe.com/webhooks?lang=node#verify-official-libraries

Implementing the Webhook API by hosting environment or framework

To perform signature verification with Stripe, the raw request body is needed. When using a framework, you need to ensure that the original body is not modified. If any changes are made to the raw request body, the verification will fail.

In the case of Hono, we can get the raw request body by the context.req.text() method. So we can create the webhook API like the following example:

ts
import Stripe from 'stripe'
import { Hono } from 'hono'
import { env } from 'hono/adapter'

const app = new Hono()

app.post('/webhook', async (context) => {
  const { STRIPE_SECRET_API_KEY, STRIPE_WEBHOOK_SECRET } =
    env(context)
  const stripe = new Stripe(STRIPE_SECRET_API_KEY)
  const signature = context.req.header('stripe-signature')
  try {
    if (!signature) {
      return context.text('', 400)
    }
    const body = await context.req.text()
    const event = await stripe.webhooks.constructEventAsync(
      body,
      signature,
      STRIPE_WEBHOOK_SECRET
    )
    switch (event.type) {
      case 'payment_intent.created': {
        console.log(event.data.object)
        break
      }
      default:
        break
    }
    return context.text('', 200)
  } catch (err) {
    const errorMessage = `⚠️  Webhook signature verification failed. ${
      err instanceof Error ? err.message : 'Internal server error'
    }`
    console.log(errorMessage)
    return context.text(errorMessage, 400)
  }
})

export default app

See also

Released under the MIT License.