🐛 Fix WhatsApp preview webhook authorization (#2499)

- Validate that WhatsApp preview webhook test sessions belong to the
authorized typebot before resuming them.
- Require the preview session to still be waiting on the requested
webhook block.
- Share WhatsApp preview phone normalization between preview creation
and test webhook execution.
This commit is contained in:
Baptiste Arnaud 2026-05-21 17:01:12 +02:00 committed by GitHub
parent e296c870bc
commit 36a6186101
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 18 additions and 11 deletions

View File

@ -8,6 +8,8 @@ import prisma from "@typebot.io/prisma";
import type { Prisma } from "@typebot.io/prisma/types";
import { isTypebotVersionAtLeastV6 } from "@typebot.io/schemas/helpers/isTypebotVersionAtLeastV6";
import { isReadTypebotForbidden } from "@typebot.io/typebot/helpers/isReadTypebotForbidden";
import { WHATSAPP_PREVIEW_SESSION_ID_PREFIX } from "@typebot.io/whatsapp/constants";
import { normalizeWhatsAppPreviewPhoneNumber } from "@typebot.io/whatsapp/normalizeWhatsAppPreviewPhoneNumber";
import { resumeWhatsAppFlow } from "@typebot.io/whatsapp/resumeWhatsAppFlow";
import { z } from "zod";
@ -15,7 +17,7 @@ export const executeTestWebhookWhatsAppInputSchema = z.object({
params: z.object({
typebotId: z.string(),
blockId: z.string(),
phone: z.string(),
phone: z.string().transform(normalizeWhatsAppPreviewPhoneNumber),
}),
body: z.unknown(),
});
@ -84,9 +86,15 @@ export const handleExecuteTestWebhookWhatsApp = async ({
message: "Webhook block not found",
});
const chatSession = await getSession(`wa-preview-${phone}`);
const chatSession = await getSession(
`${WHATSAPP_PREVIEW_SESSION_ID_PREFIX}${phone}`,
);
if (!chatSession?.state?.whatsApp)
if (
!chatSession?.state?.whatsApp ||
chatSession.state.typebotsQueue[0]?.typebot.id !== typebotId ||
chatSession.state.currentBlockId !== blockId
)
throw new ORPCError("BAD_REQUEST", {
message: "Expected whatsapp chat session",
});
@ -94,7 +102,7 @@ export const handleExecuteTestWebhookWhatsApp = async ({
await resumeWhatsAppFlow({
receivedMessages: [
{
from: chatSession.id.split("-").at(-1)!,
from: phone,
timestamp: new Date().toISOString(),
type: "webhook",
webhook: {

View File

@ -13,14 +13,11 @@ import type { User } from "@typebot.io/user/schemas";
import { sendChatReplyToWhatsApp } from "@typebot.io/whatsapp/sendChatReplyToWhatsApp";
import { sendWhatsAppMessage } from "@typebot.io/whatsapp/sendWhatsAppMessage";
import { z } from "zod";
import { WHATSAPP_PREVIEW_SESSION_ID_PREFIX } from "../constants";
import { normalizeWhatsAppPreviewPhoneNumber } from "../normalizeWhatsAppPreviewPhoneNumber";
export const startWhatsAppPreviewInputSchema = z.object({
to: z
.string()
.min(1)
.transform((value) =>
value.replace(/\s/g, "").replace(/\+/g, "").replace(/-/g, ""),
),
to: z.string().min(1).transform(normalizeWhatsAppPreviewPhoneNumber),
typebotId: z.string(),
startFrom: startFromSchema.optional(),
});
@ -72,7 +69,7 @@ export const handleStartWhatsAppPreview = async ({
)
throw new ORPCError("NOT_FOUND", { message: "Typebot not found" });
const sessionId = `wa-preview-${to}`;
const sessionId = `${WHATSAPP_PREVIEW_SESSION_ID_PREFIX}${to}`;
const existingSession = await prisma.chatSession.findFirst({
where: {

View File

@ -0,0 +1,2 @@
export const normalizeWhatsAppPreviewPhoneNumber = (phoneNumber: string) =>
phoneNumber.replace(/[\s+-]/g, "");