Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ __snapshots__
tilt_config.json
/.idea
hubspot.config.yml
AGENTS.md
CLAUDE.md
10 changes: 9 additions & 1 deletion integrations/whatsapp/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const defaultBotPhoneNumberId = {
}

export const INTEGRATION_NAME = 'whatsapp'
export const INTEGRATION_VERSION = '4.5.20'
export const INTEGRATION_VERSION = '4.7.0'
export default new IntegrationDefinition({
name: INTEGRATION_NAME,
version: INTEGRATION_VERSION,
Expand Down Expand Up @@ -266,6 +266,14 @@ export default new IntegrationDefinition({
title: 'Reply To',
description: 'The ID of the message that this message is a reply to',
},
referralSourceUrl: {
title: 'Referral Source URL',
description: 'The URL of the ad or content that led to the conversation',
},
referralSourceId: {
title: 'Referral Source ID',
description: 'The ID of the ad or content that led to the conversation',
},
},
},
conversation: {
Expand Down
2 changes: 1 addition & 1 deletion integrations/whatsapp/src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const getWabaIdsFromTokenResponseSchema = z
granular_scopes: z.array(
z.object({
scope: z.string(),
target_ids: z.array(z.string()),
target_ids: z.array(z.string()).optional(),
})
),
}),
Expand Down
31 changes: 31 additions & 0 deletions integrations/whatsapp/src/misc/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ const WhatsAppBaseMessageSchema = z.object({
id: z.string().optional(),
})
.optional(),
// there are other fields in the referral object, but we don't need them
referral: z
.object({
source_url: z.string().optional(),
source_id: z.string().optional(),
})
.optional(),
errors: z
.array(
z.object({
Expand Down Expand Up @@ -152,6 +159,29 @@ export type WhatsAppReactionMessage = WhatsAppMessage & {
type: 'reaction'
}

const WhatsAppStatusSchema = z.object({
id: z.string(),
status: z.enum(['sent', 'delivered', 'read', 'failed']),
timestamp: z.string(),
recipient_id: z.string(),
errors: z
.array(
z.object({
code: z.number(),
title: z.string(),
message: z.string(),
error_data: z
.object({
details: z.string(),
})
.optional(),
})
)
.optional(),
})

export type WhatsAppStatusValue = z.infer<typeof WhatsAppStatusSchema>

const WhatsAppMessageValueSchema = z.object({
messaging_product: z.literal('whatsapp'),
metadata: z.object({
Expand All @@ -160,6 +190,7 @@ const WhatsAppMessageValueSchema = z.object({
}),
contacts: z.array(WhatsAppContactSchema).optional(),
messages: z.array(WhatsAppMessageSchema).optional(),
statuses: z.array(WhatsAppStatusSchema).optional(),
})
export type WhatsAppMessageValue = z.infer<typeof WhatsAppMessageValueSchema>

Expand Down
4 changes: 4 additions & 0 deletions integrations/whatsapp/src/webhook/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { messagesHandler } from './handlers/messages'
import { oauthCallbackHandler } from './handlers/oauth'
import { reactionHandler } from './handlers/reaction'
import { isSandboxCommand, sandboxHandler } from './handlers/sandbox'
import { statusHandler } from './handlers/status'
import { subscribeHandler } from './handlers/subscribe'
import * as bp from '.botpress'

Expand Down Expand Up @@ -56,6 +57,9 @@ const _handler: bp.IntegrationProps['handler'] = async (props: bp.HandlerProps)

switch (changes.field) {
case 'messages':
for (const status of changes.value.statuses ?? []) {
await statusHandler(status, props)
}
for (const message of changes.value.messages ?? []) {
if (message.type === 'reaction') {
await reactionHandler(message, props)
Expand Down
37 changes: 36 additions & 1 deletion integrations/whatsapp/src/webhook/handlers/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ async function _handleIncomingMessage(
}: ValueOf<IncomingMessages> & { incomingMessageType?: string; replyTo?: string }) => {
logger.forBot().debug(`Received ${incomingMessageType ?? type} message from WhatsApp:`, payload)
return client.getOrCreateMessage({
tags: { id: message.id, replyTo },
tags: {
id: message.id,
replyTo,
..._processReferralTags(message, logger),
},
type,
payload,
userId: user.id,
Expand Down Expand Up @@ -245,3 +249,34 @@ function _getMediaExpiry(ctx: bp.Context) {
const expiresAt = new Date(Date.now() + expiryDelayHours * 60 * 60 * 1000)
return expiresAt.toISOString()
}

function _processReferralTags(message: WhatsAppMessage, logger: bp.Logger): Record<string, string> {
const { referral } = message
if (!referral) {
return {}
}

const tags: Record<string, string> = {}

if (referral.source_url) {
const originalUrl = referral.source_url
// Urls can go up to 2048 characters, but we limit to 500 to avoid tags limit error
const processedUrl = originalUrl.slice(0, 500)

if (originalUrl !== processedUrl) {
logger
.forBot()
.warn(
`For whatsapp message "${message.id}", referral source URL was truncated from ${originalUrl.length} to 500 characters. Original: ${originalUrl}, Sliced: ${processedUrl}`
)
}

tags.referralSourceUrl = processedUrl
}

if (referral.source_id) {
tags.referralSourceId = referral.source_id
}

return tags
}
22 changes: 22 additions & 0 deletions integrations/whatsapp/src/webhook/handlers/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { WhatsAppStatusValue } from '../../misc/types'
import * as bp from '.botpress'

export const statusHandler = async (value: WhatsAppStatusValue, props: bp.HandlerProps) => {
const { logger } = props

if (value.status === 'failed') {
const errorDetails =
value.errors
?.map(
(err) =>
`${err.title} (${err.code}): ${err.message}${err.error_data?.details ? ` - ${err.error_data.details}` : ''}`
)
.join('; ') || 'Unknown error'

logger
.forBot()
.error(
`WhatsApp message delivery failed. Message ID: ${value.id}, Recipient: ${value.recipient_id}, Errors: ${errorDetails}`
)
}
}
Loading