ZEON256 ZEON256
← BACK

Handling Stripe Webhooks in Elysia

ZEON256
ZEON256 2025-11-07 1 min read
typescript elysia stripe webhooks
Target Audience Web developers Elysia/Bun users Payment integration engineers
AI Summary
How to handle Stripe webhooks in Elysia when the framework's body schema validation consumes the raw body before Stripe can verify the signature. The key workaround: omit the body from your route schema and instead read it directly via request.text(), then pass the raw string to Stripe's constructEventAsync for verification. Covers signature checking, event construction, and error handling.

Overview

I was recently stuck trying to handle stripe webhooks in Elysia. None of the AI tools and existing reddit post could solve it. This is the solution I found, hopefully it’s helpful for those that need it.

Note

Elysia Version: 1.4.6

Code

TypeScript
import { type } from "arktype";

// Note: you dont specify your body in the schema!
const WebhookSchema = {
  headers: type({ "stripe-signature": "string" }),
};

export function stripeRoute({ sql, logger, stripeConfig }: StripeRouteParams) {
  const { stripe, webhookSecret } = stripeConfig;

  const webhookService = new Elysia().post(
    "/webhooks",
    async ({ request, headers, set }) => {
      // you have to do this!
      const body = await request.text();

      logger.debug("Received webhook request");

      const signature = headers["stripe-signature"];

      if (!signature) {
        logger.warn("Webhook received without Stripe signature");
        set.status = 400;
        return { error: "No signature provided" };
      }

      let event: Stripe.Event;
      try {
        event = await stripe.webhooks.constructEventAsync(body, signature, webhookSecret);
      } catch (err) {
        logger.error({ error: err }, "Webhook signature verification failed");
        set.status = 400;
        return {
          error: `Webhook Error: ${err instanceof Error ? err.message : "Unknown error"}`,
        };
      }

      logger.info({ eventType: event.type, eventId: event.id }, "Processing Stripe webhook event");

      try {
        /* handle your events here */
        set.status = 200;
        return { received: true };
      } catch (error) {
        logger.error(
          { error, eventType: event.type, eventId: event.id },
          "Error processing webhook",
        );
        set.status = 500;
        return { error: "Webhook processing failed" };
      }
    },
    WebhookSchema,
  );
}