chore: Add logging eventId from Stripe webhook (#26623)

* Add loggin

* fix: use established logger instead of console.log for Stripe webhook eventId

Co-Authored-By: unknown <>

* fix: remove sensitive stripe-signature header from error logs

Co-Authored-By: unknown <>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Joe Au-Yeung
2026-01-12 14:02:35 +00:00
committed by GitHub
co-authored by unknown Devin AI
parent c5cb2edd65
commit d5c2212c41
+84 -18
View File
@@ -8,8 +8,13 @@ import {
eventTypeMetaDataSchemaWithTypedApps,
eventTypeAppMetadataOptionalSchema,
} from "@calcom/app-store/zod-utils";
import { sendAttendeeRequestEmailAndSMS, sendOrganizerRequestEmail } from "@calcom/emails/email-manager";
import EventManager, { placeholderCreatedEvent } from "@calcom/features/bookings/lib/EventManager";
import {
sendAttendeeRequestEmailAndSMS,
sendOrganizerRequestEmail,
} from "@calcom/emails/email-manager";
import EventManager, {
placeholderCreatedEvent,
} from "@calcom/features/bookings/lib/EventManager";
import { doesBookingRequireConfirmation } from "@calcom/features/bookings/lib/doesBookingRequireConfirmation";
import { getAllCredentialsIncludeServiceAccountKey } from "@calcom/features/bookings/lib/getAllCredentialsForUsersOnEvent/getAllCredentials";
import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirmation";
@@ -36,7 +41,10 @@ export const config = {
},
};
export async function handleStripePaymentSuccess(event: Stripe.Event, traceContext: TraceContext) {
export async function handleStripePaymentSuccess(
event: Stripe.Event,
traceContext: TraceContext
) {
const paymentIntent = event.data.object as Stripe.PaymentIntent;
const payment = await prisma.payment.findFirst({
where: {
@@ -49,10 +57,15 @@ export async function handleStripePaymentSuccess(event: Stripe.Event, traceConte
});
if (!payment?.bookingId) {
log.error("Stripe: Payment Not Found", safeStringify(paymentIntent), safeStringify(payment));
log.error(
"Stripe: Payment Not Found",
safeStringify(paymentIntent),
safeStringify(payment)
);
throw new HttpCode({ statusCode: 204, message: "Payment not found" });
}
if (!payment?.bookingId) throw new HttpCode({ statusCode: 204, message: "Payment not found" });
if (!payment?.bookingId)
throw new HttpCode({ statusCode: 204, message: "Payment not found" });
await handlePaymentSuccess({
paymentId: payment.id,
@@ -62,7 +75,10 @@ export async function handleStripePaymentSuccess(event: Stripe.Event, traceConte
});
}
const handleSetupSuccess = async (event: Stripe.Event, traceContext: TraceContext) => {
const handleSetupSuccess = async (
event: Stripe.Event,
traceContext: TraceContext
) => {
const setupIntent = event.data.object as Stripe.SetupIntent;
const payment = await prisma.payment.findFirst({
where: {
@@ -70,7 +86,8 @@ const handleSetupSuccess = async (event: Stripe.Event, traceContext: TraceContex
},
});
if (!payment?.data || !payment?.id) throw new HttpCode({ statusCode: 204, message: "Payment not found" });
if (!payment?.data || !payment?.id)
throw new HttpCode({ statusCode: 204, message: "Payment not found" });
const { booking, user, evt, eventType } = await getBooking(payment.bookingId);
@@ -91,7 +108,9 @@ const handleSetupSuccess = async (event: Stripe.Event, traceContext: TraceContex
},
});
const metadata = eventTypeMetaDataSchemaWithTypedApps.parse(eventType?.metadata);
const metadata = eventTypeMetaDataSchemaWithTypedApps.parse(
eventType?.metadata
);
const allCredentials = await getAllCredentialsIncludeServiceAccountKey(user, {
...booking.eventType,
metadata,
@@ -101,11 +120,15 @@ const handleSetupSuccess = async (event: Stripe.Event, traceContext: TraceContex
const platformOAuthClient = user.isPlatformManaged
? await platformOAuthClientRepository.getByUserId(user.id)
: null;
const areCalendarEventsEnabled = platformOAuthClient?.areCalendarEventsEnabled ?? true;
const areCalendarEventsEnabled =
platformOAuthClient?.areCalendarEventsEnabled ?? true;
const areEmailsEnabled = platformOAuthClient?.areEmailsEnabled ?? true;
if (!requiresConfirmation) {
const eventManager = new EventManager({ ...user, credentials: allCredentials }, metadata?.apps);
const eventManager = new EventManager(
{ ...user, credentials: allCredentials },
metadata?.apps
);
const scheduleResult = areCalendarEventsEnabled
? await eventManager.create(evt)
: placeholderCreatedEvent;
@@ -142,18 +165,27 @@ const handleSetupSuccess = async (event: Stripe.Event, traceContext: TraceContex
bookingId: booking.id,
booking,
paid: true,
platformClientParams: platformOAuthClient ? getPlatformParams(platformOAuthClient) : undefined,
platformClientParams: platformOAuthClient
? getPlatformParams(platformOAuthClient)
: undefined,
traceContext: updatedTraceContext,
actionSource: "WEBHOOK",
actor,
});
} else if (areEmailsEnabled) {
await sendOrganizerRequestEmail({ ...evt }, eventType.metadata);
await sendAttendeeRequestEmailAndSMS({ ...evt }, evt.attendees[0], eventType.metadata);
await sendAttendeeRequestEmailAndSMS(
{ ...evt },
evt.attendees[0],
eventType.metadata
);
}
};
type WebhookHandler = (event: Stripe.Event, traceContext: TraceContext) => Promise<void>;
type WebhookHandler = (
event: Stripe.Event,
traceContext: TraceContext
) => Promise<void>;
const webhookHandlers: Record<string, WebhookHandler | undefined> = {
"payment_intent.succeeded": handleStripePaymentSuccess,
@@ -165,23 +197,54 @@ const webhookHandlers: Record<string, WebhookHandler | undefined> = {
* We need to create a PaymentManager in `@calcom/lib`
* to prevent circular dependencies on App Store migration
*/
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
if (req.method !== "POST") {
throw new HttpCode({ statusCode: 405, message: "Method Not Allowed" });
}
const sig = req.headers["stripe-signature"];
if (!sig) {
throw new HttpCode({ statusCode: 400, message: "Missing stripe-signature" });
throw new HttpCode({
statusCode: 400,
message: "Missing stripe-signature",
});
}
if (!process.env.STRIPE_WEBHOOK_SECRET) {
throw new HttpCode({ statusCode: 500, message: "Missing process.env.STRIPE_WEBHOOK_SECRET" });
throw new HttpCode({
statusCode: 500,
message: "Missing process.env.STRIPE_WEBHOOK_SECRET",
});
}
const requestBuffer = await buffer(req);
const payload = requestBuffer.toString();
const event = stripe.webhooks.constructEvent(payload, sig, process.env.STRIPE_WEBHOOK_SECRET);
let stripeEventId: string | undefined;
try {
const parsedPayload = JSON.parse(payload);
stripeEventId = parsedPayload?.id;
log.debug("Stripe webhook eventId", { stripeEventId });
} catch {
// Ignore parse errors, this is just for debugging
}
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
payload,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
log.error("Stripe webhook signature verification failed", {
stripeEventId,
error: err instanceof Error ? err.message : "Unknown error",
});
throw err;
}
const traceContext = distributedTracing.createTrace(`stripe_webhook`, {
meta: {
@@ -193,7 +256,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// bypassing this validation for e2e tests
// in order to successfully confirm the payment
if (!event.account && !process.env.NEXT_PUBLIC_IS_E2E) {
throw new HttpCode({ statusCode: 202, message: "Incoming connected account" });
throw new HttpCode({
statusCode: 202,
message: "Incoming connected account",
});
}
const handler = webhookHandlers[event.type];