🐛 (whatsapp) Fix auto start input where it didn't display next bu… (#869)
…bbles <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ### Summary by CodeRabbit **Release Notes** - New Feature: Enhanced WhatsApp integration with improved phone number formatting and session ID generation. - Refactor: Updated the `startWhatsAppPreview` and `receiveMessagePreview` functions for better consistency and readability. - Bug Fix: Added a check for `phoneNumberId` in the `receiveMessage` function to prevent errors when it's undefined. - Documentation: Expanded the WhatsApp integration guide and FAQs in the docs, providing more detailed instructions and addressing common queries. - Chore: Introduced a new `metadata` field in the `whatsAppWebhookRequestBodySchema` to store the `phone_number_id`. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@ -6,8 +6,8 @@ import {
|
||||
Alert,
|
||||
AlertIcon,
|
||||
Button,
|
||||
Flex,
|
||||
HStack,
|
||||
Link,
|
||||
SlideFade,
|
||||
Stack,
|
||||
StackProps,
|
||||
@ -84,28 +84,38 @@ export const WhatsAppPreviewInstructions = (props: StackProps) => {
|
||||
defaultValue={phoneNumber}
|
||||
onChange={setPhoneNumber}
|
||||
/>
|
||||
<Button
|
||||
isDisabled={isEmpty(phoneNumber) || isMessageSent}
|
||||
isLoading={isSendingMessage}
|
||||
type="submit"
|
||||
>
|
||||
{hasMessageBeenSent ? 'Restart' : 'Start'} the chat
|
||||
</Button>
|
||||
{!isMessageSent && (
|
||||
<Button
|
||||
isDisabled={isEmpty(phoneNumber) || isMessageSent}
|
||||
isLoading={isSendingMessage}
|
||||
type="submit"
|
||||
>
|
||||
{hasMessageBeenSent ? 'Restart' : 'Start'} the chat
|
||||
</Button>
|
||||
)}
|
||||
<SlideFade offsetY="20px" in={isMessageSent} unmountOnExit>
|
||||
<Flex>
|
||||
<Stack>
|
||||
<Alert status="success" w="100%">
|
||||
<HStack>
|
||||
<AlertIcon />
|
||||
<Stack spacing={1}>
|
||||
<Text fontWeight="semibold">Chat started!</Text>
|
||||
<Text fontSize="sm">
|
||||
Open WhatsApp to test your bot. The first message can take up
|
||||
to 2 min to be delivered.
|
||||
The first message can take up to 2 min to be delivered.
|
||||
</Text>
|
||||
</Stack>
|
||||
</HStack>
|
||||
</Alert>
|
||||
</Flex>
|
||||
<Button
|
||||
as={Link}
|
||||
href={`https://web.whatsapp.com/`}
|
||||
isExternal
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
>
|
||||
Open WhatsApp Web
|
||||
</Button>
|
||||
</Stack>
|
||||
</SlideFade>
|
||||
</Stack>
|
||||
)
|
||||
|
@ -47,7 +47,7 @@ export const getPhoneNumber = authenticatedProcedure
|
||||
|
||||
const formattedPhoneNumber = `${
|
||||
display_phone_number.startsWith('+') ? '' : '+'
|
||||
}${display_phone_number.replace(/\s-/g, '')}`
|
||||
}${display_phone_number.replace(/[\s-]/g, '')}`
|
||||
|
||||
return {
|
||||
id: credentials.phoneNumberId,
|
||||
|
@ -34,7 +34,7 @@ export const receiveMessagePreview = publicProcedure
|
||||
const contactPhoneNumber = '+' + receivedMessage.from
|
||||
return resumeWhatsAppFlow({
|
||||
receivedMessage,
|
||||
sessionId: `wa-${receivedMessage.from}-preview`,
|
||||
sessionId: `wa-preview-${receivedMessage.from}`,
|
||||
contact: {
|
||||
name: contactName,
|
||||
phoneNumber: contactPhoneNumber,
|
||||
|
@ -27,7 +27,9 @@ export const startWhatsAppPreview = authenticatedProcedure
|
||||
to: z
|
||||
.string()
|
||||
.min(1)
|
||||
.transform((value) => value.replace(/\s/g, '').replace(/\+/g, '')),
|
||||
.transform((value) =>
|
||||
value.replace(/\s/g, '').replace(/\+/g, '').replace(/-/g, '')
|
||||
),
|
||||
typebotId: z.string(),
|
||||
startGroupId: z.string().optional(),
|
||||
})
|
||||
@ -70,7 +72,7 @@ export const startWhatsAppPreview = authenticatedProcedure
|
||||
)
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
|
||||
|
||||
const sessionId = `wa-${to}-preview`
|
||||
const sessionId = `wa-preview-${to}`
|
||||
|
||||
const existingSession = await prisma.chatSession.findFirst({
|
||||
where: {
|
||||
@ -130,7 +132,7 @@ export const startWhatsAppPreview = authenticatedProcedure
|
||||
whatsApp: (existingSession?.state as SessionState | undefined)
|
||||
?.whatsApp,
|
||||
},
|
||||
id: `wa-${to}-preview`,
|
||||
id: sessionId,
|
||||
})
|
||||
try {
|
||||
await sendWhatsAppMessage({
|
||||
|
@ -17,3 +17,5 @@ It is possible that Meta automatically restricts your newly created Business acc
|
||||
4. Select `Business` type
|
||||
5. Give it any name and select your newly created Business Account
|
||||
6. On the app page, look for `WhatsApp` product and enable it
|
||||
|
||||
You can then follow the instructions in the Share tab of your bot to connect your Meta app to Typebot.
|
||||
|
@ -29,8 +29,32 @@ WhatsApp environment have some limitations that you need to keep in mind when bu
|
||||
- Google Analytics block
|
||||
- Meta Pixel blocks
|
||||
|
||||
## Configuration
|
||||
|
||||
You can customize how your bot behaves on WhatsApp in the `Configure integration` section
|
||||
|
||||
<img src="/img/whatsapp/configure-integration.png" alt="WhatsApp configure integration" />
|
||||
|
||||
**Session expiration timeout**: A number from 0 to 48 which is the number of hours after which the session will expire. If the user doesn't interact with the bot for more than the timeout, the session will expire and if user sends a new message, it will start a new chat.
|
||||
|
||||
**Start bot condition**: A condition that will be evaluated when a user starts a conversation with your bot. If the condition is not met, the bot will not be triggered.
|
||||
|
||||
## Contact information
|
||||
|
||||
You can automatically assign contact name and phone number to a variable in your bot using a Set variable block with the dedicated system values:
|
||||
|
||||
<img src="/img/whatsapp/contact-var.png" alt="WhatsApp contact system variables" />
|
||||
|
||||
## FAQ
|
||||
|
||||
### How many WhatsApp numbers can I use?
|
||||
|
||||
You can integrate as many numbers as you'd like. Keep in mind that Typebot does not provide those numbers. We work as a "Bring your own Meta application" and we give you clear instructions on [how to set up your Meta app](./whatsapp/create-meta-app).
|
||||
|
||||
### Can I link multiple bots to the same WhatsApp number?
|
||||
|
||||
Yes, you can. You will have to add a "Start bot condition" to each of your bots to make sure that the right bot is triggered when a user starts a conversation.
|
||||
|
||||
### Does the integration with WhatsApp requires any paid API?
|
||||
|
||||
You integrate your typebots with your own WhatsApp Business Platform which is the official service from Meta. At the moment, the first 1,000 Service conversations each month are free. For more information, refer to [their documentation](https://developers.facebook.com/docs/whatsapp/cloud-api/get-started#pricing---payment-methods)
|
||||
|
@ -33087,6 +33087,18 @@
|
||||
"value": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"phone_number_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"phone_number_id"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"contacts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -33388,6 +33400,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"metadata"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
|
@ -5746,7 +5746,14 @@
|
||||
"type": "string"
|
||||
},
|
||||
"pixelId": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "Deprecated"
|
||||
},
|
||||
"pixelIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"gtmId": {
|
||||
"type": "string"
|
||||
@ -6396,28 +6403,48 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filePathProps": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"typebotId": {
|
||||
"type": "string"
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"typebotId": {
|
||||
"type": "string"
|
||||
},
|
||||
"blockId": {
|
||||
"type": "string"
|
||||
},
|
||||
"resultId": {
|
||||
"type": "string"
|
||||
},
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"typebotId",
|
||||
"blockId",
|
||||
"resultId",
|
||||
"fileName"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"blockId": {
|
||||
"type": "string"
|
||||
},
|
||||
"resultId": {
|
||||
"type": "string"
|
||||
},
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionId": {
|
||||
"type": "string"
|
||||
},
|
||||
"fileName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionId",
|
||||
"fileName"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"typebotId",
|
||||
"blockId",
|
||||
"resultId",
|
||||
"fileName"
|
||||
],
|
||||
"additionalProperties": false
|
||||
]
|
||||
},
|
||||
"fileType": {
|
||||
"type": "string"
|
||||
@ -6604,6 +6631,18 @@
|
||||
"value": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"phone_number_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"phone_number_id"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"contacts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -6905,6 +6944,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"metadata"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
|
BIN
apps/docs/static/img/whatsapp/configure-integration.png
vendored
Normal file
BIN
apps/docs/static/img/whatsapp/configure-integration.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
@ -23,16 +23,19 @@ export const receiveMessage = publicProcedure
|
||||
message: z.string(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input: { entry, workspaceId, credentialsId } }) => {
|
||||
.mutation(async ({ input: { entry, credentialsId, workspaceId } }) => {
|
||||
const receivedMessage = entry.at(0)?.changes.at(0)?.value.messages?.at(0)
|
||||
if (isNotDefined(receivedMessage)) return { message: 'No message found' }
|
||||
const contactName =
|
||||
entry.at(0)?.changes.at(0)?.value?.contacts?.at(0)?.profile?.name ?? ''
|
||||
const contactPhoneNumber =
|
||||
entry.at(0)?.changes.at(0)?.value?.messages?.at(0)?.from ?? ''
|
||||
const phoneNumberId = entry.at(0)?.changes.at(0)?.value
|
||||
.metadata.phone_number_id
|
||||
if (!phoneNumberId) return { message: 'No phone number id found' }
|
||||
return resumeWhatsAppFlow({
|
||||
receivedMessage,
|
||||
sessionId: `wa-${credentialsId}-${receivedMessage.from}`,
|
||||
sessionId: `wa-${phoneNumberId}-${receivedMessage.from}`,
|
||||
credentialsId,
|
||||
workspaceId,
|
||||
contact: {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { ChatSession, sessionStateSchema } from '@typebot.io/schemas'
|
||||
import { sessionStateSchema } from '@typebot.io/schemas'
|
||||
|
||||
export const getSession = async (sessionId: string) => {
|
||||
const session = await prisma.chatSession.findUnique({
|
||||
|
@ -71,7 +71,6 @@ export const resumeWhatsAppFlow = async ({
|
||||
: workspaceId
|
||||
? await startWhatsAppSession({
|
||||
incomingMessage: messageContent,
|
||||
sessionId,
|
||||
workspaceId,
|
||||
credentials: { ...credentials, id: credentialsId as string },
|
||||
contact,
|
||||
|
@ -20,7 +20,6 @@ import { upsertResult } from '../queries/upsertResult'
|
||||
|
||||
type Props = {
|
||||
incomingMessage?: string
|
||||
sessionId: string
|
||||
workspaceId?: string
|
||||
credentials: WhatsAppCredentials['data'] & Pick<WhatsAppCredentials, 'id'>
|
||||
contact: NonNullable<SessionState['whatsApp']>['contact']
|
||||
@ -76,7 +75,7 @@ export const startWhatsAppSession = async ({
|
||||
publicTypebot.settings.whatsApp?.sessionExpiryTimeout ??
|
||||
defaultSessionExpiryTimeout
|
||||
|
||||
const session = await startSession({
|
||||
let chatReply = await startSession({
|
||||
startParams: {
|
||||
typebot: publicTypebot.typebot.publicId as string,
|
||||
},
|
||||
@ -89,34 +88,29 @@ export const startWhatsAppSession = async ({
|
||||
},
|
||||
})
|
||||
|
||||
let newSessionState: SessionState = session.newSessionState
|
||||
const sessionState: SessionState = chatReply.newSessionState
|
||||
|
||||
// If first block is an input block, we can directly continue the bot flow
|
||||
const firstEdgeId =
|
||||
newSessionState.typebotsQueue[0].typebot.groups[0].blocks[0].outgoingEdgeId
|
||||
const nextGroup = await getNextGroup(newSessionState)(firstEdgeId)
|
||||
sessionState.typebotsQueue[0].typebot.groups[0].blocks[0].outgoingEdgeId
|
||||
const nextGroup = await getNextGroup(sessionState)(firstEdgeId)
|
||||
const firstBlock = nextGroup.group?.blocks.at(0)
|
||||
if (firstBlock && isInputBlock(firstBlock)) {
|
||||
const resultId = newSessionState.typebotsQueue[0].resultId
|
||||
const resultId = sessionState.typebotsQueue[0].resultId
|
||||
if (resultId)
|
||||
await upsertResult({
|
||||
hasStarted: true,
|
||||
isCompleted: false,
|
||||
resultId,
|
||||
typebot: newSessionState.typebotsQueue[0].typebot,
|
||||
typebot: sessionState.typebotsQueue[0].typebot,
|
||||
})
|
||||
newSessionState = (
|
||||
await continueBotFlow({
|
||||
...newSessionState,
|
||||
currentBlock: { groupId: firstBlock.groupId, blockId: firstBlock.id },
|
||||
})(incomingMessage)
|
||||
).newSessionState
|
||||
chatReply = await continueBotFlow({
|
||||
...sessionState,
|
||||
currentBlock: { groupId: firstBlock.groupId, blockId: firstBlock.id },
|
||||
})(incomingMessage)
|
||||
}
|
||||
|
||||
return {
|
||||
...session,
|
||||
newSessionState,
|
||||
}
|
||||
return chatReply
|
||||
}
|
||||
|
||||
export const messageMatchStartCondition = (
|
||||
|
@ -142,6 +142,9 @@ export const whatsAppWebhookRequestBodySchema = z.object({
|
||||
changes: z.array(
|
||||
z.object({
|
||||
value: z.object({
|
||||
metadata: z.object({
|
||||
phone_number_id: z.string(),
|
||||
}),
|
||||
contacts: z
|
||||
.array(
|
||||
z.object({
|
||||
|
Reference in New Issue
Block a user