Skip to content

Commit

Permalink
🐛 (whatsapp) Fix auto start input where it didn't display next bu… (#869
Browse files Browse the repository at this point in the history
)

…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 -->
  • Loading branch information
baptisteArno authored Sep 29, 2023
1 parent 76f4954 commit f9a14c0
Show file tree
Hide file tree
Showing 14 changed files with 153 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
Alert,
AlertIcon,
Button,
Flex,
HStack,
Link,
SlideFade,
Stack,
StackProps,
Expand Down Expand Up @@ -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>
)
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/src/features/whatsapp/getPhoneNumber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 5 additions & 3 deletions apps/builder/src/features/whatsapp/startWhatsAppPreview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
})
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -130,7 +132,7 @@ export const startWhatsAppPreview = authenticatedProcedure
whatsApp: (existingSession?.state as SessionState | undefined)
?.whatsApp,
},
id: `wa-${to}-preview`,
id: sessionId,
})
try {
await sendWhatsAppMessage({
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/docs/embed/whatsapp/create-meta-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
24 changes: 24 additions & 0 deletions apps/docs/docs/embed/whatsapp/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
15 changes: 15 additions & 0 deletions apps/docs/openapi/builder/_spec_.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -33388,6 +33400,9 @@
}
}
},
"required": [
"metadata"
],
"additionalProperties": false
}
},
Expand Down
84 changes: 63 additions & 21 deletions apps/docs/openapi/chat/_spec_.json
Original file line number Diff line number Diff line change
Expand Up @@ -5746,7 +5746,14 @@
"type": "string"
},
"pixelId": {
"type": "string"
"type": "string",
"description": "Deprecated"
},
"pixelIds": {
"type": "array",
"items": {
"type": "string"
}
},
"gtmId": {
"type": "string"
Expand Down Expand Up @@ -6396,28 +6403,48 @@
"type": "object",
"properties": {
"filePathProps": {
"type": "object",
"properties": {
"typebotId": {
"type": "string"
},
"blockId": {
"type": "string"
},
"resultId": {
"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
},
"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"
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -6905,6 +6944,9 @@
}
}
},
"required": [
"metadata"
],
"additionalProperties": false
}
},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions apps/viewer/src/features/whatsapp/api/receiveMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion packages/bot-engine/queries/getSession.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand Down
1 change: 0 additions & 1 deletion packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export const resumeWhatsAppFlow = async ({
: workspaceId
? await startWhatsAppSession({
incomingMessage: messageContent,
sessionId,
workspaceId,
credentials: { ...credentials, id: credentialsId as string },
contact,
Expand Down
28 changes: 11 additions & 17 deletions packages/bot-engine/whatsapp/startWhatsAppSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down Expand Up @@ -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,
},
Expand All @@ -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 = (
Expand Down
3 changes: 3 additions & 0 deletions packages/schemas/features/whatsapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down

4 comments on commit f9a14c0

@vercel
Copy link

@vercel vercel bot commented on f9a14c0 Sep 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

builder-v2-git-main-typebot-io.vercel.app
builder-v2-typebot-io.vercel.app
app.typebot.io

@vercel
Copy link

@vercel vercel bot commented on f9a14c0 Sep 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

docs – ./apps/docs

docs-typebot-io.vercel.app
docs.typebot.io
docs-git-main-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on f9a14c0 Sep 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on f9a14c0 Sep 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viewer-v2 – ./apps/viewer

bii.bj
1stop.au
houss.io
wasap.nl
x.cr8.ai
appmillion.online
averdadehoje.site
bot.cerograsa.com
bot.chatbotcv.com
bot.hostnation.de
bot.ketoolife.com
bot.phuonghub.com
bot.reviewamp.com
bot.reviewzer.com
bot.uluhub.com.br
cares.urlabout.me
chat.ezbooking.ai
chat.gaswadern.de
chat.gniorder.com
chat.leadmagic.io
chat.onrentme.com
chat.rojie.online
chatdocidadao.com
chatwebonline.com
ds-leadmagnet.com
fmm.wpwakanda.com
footballmeetup.ie
gentleman-shop.fr
island.wakanda.is
k1.kandabrand.com
kp.pedroknoll.com
kylebot.goafk.com
lb.ticketfute.com
mariwelash.com.br
metodoelev.com.br
nutriandreia.shop
order.chatjer.com
ov1.wpwakanda.com
ov2.wpwakanda.com
ov3.wpwakanda.com
pcb.drapamela.com
softwarelucra.com
support.triplo.ai
survey.collab.day
test.eqfeqfeq.com
viewer.typebot.io
welcome.triplo.ai
www.thegymgame.it
zeropendencia.com
1988.bouclidom.com
a.onewebcenter.com
amancarseat.online
amostra-safe.click
andreimayer.com.br
viewer-v2-typebot-io.vercel.app
download.thailandmicespecialist.com
mdb.assessoria.aloisio.progenbr.com
mdb.assessoria.girotto.progenbr.com
mdb.assessoria.marinho.progenbr.com
mdb.assessoria.rodrigo.progenbr.com
register.thailandmicespecialist.com
mdb.assessoria.desideri.progenbr.com
mdb.assessoria.fernanda.progenbr.com
mdb.assessoria.jbatista.progenbr.com
mdb.assessoria.mauricio.progenbr.com
mdb.evento.autocadastro.progenbr.com
form.shopmercedesbenzsouthorlando.com
mdb.evento.equipeinterna.progenbr.com
bot.studiotecnicoimmobiliaremerelli.it
mdb.assessoria.boaventura.progenbr.com
mdb.assessoria.jtrebesqui.progenbr.com
pesquisa.escolamodacomproposito.com.br
anamnese.clinicaramosodontologia.com.br
gabinete.baleia.formulario.progenbr.com
mdb.assessoria.carreirinha.progenbr.com
chrome-os-inquiry-system.itschromeos.com
mdb.assessoria.paulomarques.progenbr.com
viewer-v2-git-main-typebot-io.vercel.app
main-menu-for-itschromeos.itschromeos.com
mdb.assessoria.qrcode.ademir.progenbr.com
mdb.assessoria.qrcode.arthur.progenbr.com
mdb.assessoria.qrcode.danilo.progenbr.com
mdb.assessoria.qrcode.marcao.progenbr.com
mdb.assessoria.qrcode.marcio.progenbr.com
mdb.assessoria.qrcode.aloisio.progenbr.com
mdb.assessoria.qrcode.girotto.progenbr.com
mdb.assessoria.qrcode.marinho.progenbr.com
mdb.assessoria.qrcode.rodrigo.progenbr.com
mdb.assessoria.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.desideri.progenbr.com
mdb.assessoria.qrcode.fernanda.progenbr.com
mdb.assessoria.qrcode.jbatista.progenbr.com
mdb.assessoria.qrcode.mauricio.progenbr.com
mdb.assessoria.fernanda.regional.progenbr.com
mdb.assessoria.qrcode.boaventura.progenbr.com
mdb.assessoria.qrcode.jtrebesqui.progenbr.com
mdb.assessoria.qrcode.carreirinha.progenbr.com
mdb.assessoria.qrcode.paulomarques.progenbr.com
mdb.assessoria.qrcode.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.fernanda.regional.progenbr.com

Please sign in to comment.