Reputation: 11
How i can handle the stripe webhook with ElysiaJS?
i have been trying for a long time and dont get results
im here, the body is parsed and i received an string from the reqText
the contructEvent returns this error Webhook payload must be provided as a string or a Buffer (https://nodejs.org/api/buffer.html) instance representing the raw request body.Payload was provided as a parsed JavaScript object instead. Signature verification is impossible without access to the original signed material.
Learn more about webhook signing and explore webhook integration examples for various frameworks at https://github.com/stripe/stripe-node#webhook-signing
import { Elysia, ParseError, t } from 'elysia'
import { env } from '@/config/env.config'
import { stripeClient } from '@/lib/stripe.lib'
export const stripeWebhook = new Elysia().post(
'/integrations/stripe/webhook',
async () => {},
{
headers: t.Object({
'stripe-signature': t.String(),
'content-type': t.String(),
}),
async parse({ headers, request }) {
if (headers['content-type'] === 'application/json; charset=utf-8') {
const reqText = await request.text()
return webhookHandler(reqText, request)
} else {
throw new ParseError('Invalid content type')
}
},
body: t.Not(t.Undefined()),
},
)
const webhookHandler = async (reqText: string, request: Request) => {
const endpointSecret = env.STRIPE_WEBHOOK_SECRET
const signature = request.headers.get('stripe-signature')
if (!signature) {
console.error('Stripe signature is missing')
return {
statusCode: 400,
body: JSON.stringify({ error: 'Stripe signature is missing' }),
}
}
if (!stripeClient) {
console.error('Stripe client is not initialized')
return {
statusCode: 500,
body: JSON.stringify({ error: 'Stripe client is not initialized' }),
}
}
try {
const event = await stripeClient.webhooks.constructEventAsync(
reqText,
signature,
endpointSecret,
)
if (event.type === 'payment_intent.succeeded') {
console.log(event.object)
console.log('PaymentIntent was successful!')
}
return {
statusCode: 200,
body: JSON.stringify({ received: true }),
}
} catch (error) {
console.error(error)
return {
statusCode: 400,
body: `Webhook Error: ${error}`,
}
}
}
i want make it works properly
Upvotes: 1
Views: 606
Reputation: 1
All you need to do is to put .onParse()
in front of the post request.
Since Stripe
need raw data but it seem like Elysia just parse it into JSON object.
So I just put this
.onParse(...)
in front of webhook
and it seem work.
This is not my code I stole it from reddit hahaha.
example.
.onParse(async ({ request, headers }) => {
if (headers["content-type"] === "application/json; charset=utf-8") {
const arrayBuffer = await Bun.readableStreamToArrayBuffer(request.body!);
const rawBody = Buffer.from(arrayBuffer);
return rawBody
}
})
.post('/webhook', async ( { body, headers, request } ) => {
const signature = headers['stripe-signature'];
// try {
const event = await stripe.webhooks.constructEventAsync(
body,
signature,
endpointSecret
);
console.log( event );
return {
status: 'success',
};
credit: get raw data to stripe
Upvotes: 0
Reputation: 131
Elysia provides the raw body in the request's array buffer.
import { Elysia } from "elysia";
import assert from "assert";
import Stripe from "stripe";
const stripe = new Stripe("sk_...");
const app = new Elysia()
.post("/stripe", async ({ request }) => {
const signature = request.headers.get("Stripe-Signature");
assert(signature != null, "Stripe Signature is needed");
const body = await request.arrayBuffer();
const event = await stripe.webhooks.constructEventAsync(
Buffer.from(body),
signature,
"whsec_...", // replace with yours
);
console.log("event type is", event.type);
return "ok";
})
.onError(({ error }) => {
console.error(error);
return new Response("Internal Server Error", { status: 500 });
})
Upvotes: 0
Reputation: 31
just add type: 'arrayBuffer'
and then when you want to pass the body:
Buffer.from(body as ArrayBuffer)
The result would be something like this:
.post(
'/webhook',
async ({ body, headers }) => {
let event;
// Verify webhook signature and extract the event.
try {
const endpointSecret = // your secret
const sig = headers['stripe-signature'];
event = await stripe.webhooks.constructEventAsync(Buffer.from(body as ArrayBuffer), sig as any, endpointSecret as any);
} catch (err) {
throw new Error(`Webhook Error: ${err}`);
}
// Handle the event
return { received: true };
},
{
type: 'arrayBuffer'
}
);
Upvotes: -1
Reputation: 161
I struggled with this for a long time. request.body
is a ReadableStream, so not exactly what the stripe webhook wants. The trick is to override the parse handler to read the raw body instead of doing :
new Elysia()
.post(
"/stripe/webhook",
async ({
request,
body,
error,
headers: { "stripe-signature": stripeSignature },
}) => {
if (!request.body) {
error(400);
return;
}
return handlerThatCallsConstructEvent(
body as Buffer, stripeSignature, error);
},
{
headers: t.Object({ "stripe-signature": t.String() }),
async parse(ctx) {
if (ctx.request.body === null) {
return;
}
return Buffer.from(
await Bun.readableStreamToArrayBuffer(ctx.request.body),
);
},
},
),
That's kind of hard to read. the .post function takes three parameters:
async parse(ctx)
-> reads the raw body as a buffer and returns it with no extra parsing.Upvotes: -1