Skip to content

WhatsApp

Overview#

The WhatsApp integration connects Cognest to the WhatsApp Business Platform via the Cloud API. It supports sending text, template, and media messages, receiving inbound messages through webhooks, and tracking delivery status updates. Messages are routed through Meta's infrastructure and comply with WhatsApp's 24-hour conversation window policy.

Prerequisites#

  1. A Meta Business account with a registered WhatsApp Business App
  2. A phone number registered through the WhatsApp Business Platform
  3. A permanent access token generated from the Meta Developer Portal
  4. A publicly accessible HTTPS endpoint for webhook delivery (or use cognest tunnel during development)

Test Numbers

Meta provides a test phone number and up to 5 recipient numbers during development. You can send messages to these numbers without incurring charges. Move to a production phone number when you are ready to go live.

Configuration#

Add the WhatsApp integration to your cognest.config.yaml:

cognest.config.yaml
integrations:
  whatsapp:
    enabled: true
    credentials:
      phone_number_id: ${WHATSAPP_PHONE_ID}
      access_token: ${WHATSAPP_ACCESS_TOKEN}
      webhook_verify_token: ${WEBHOOK_VERIFY_TOKEN}
    settings:
      api_version: "v19.0"
      webhook_path: "/webhooks/whatsapp"
      message_timeout: 30000
      retry_attempts: 3
      read_receipts: true

Store your credentials in environment variables. Never commit secrets to version control:

.env
# .env
WHATSAPP_PHONE_ID=102938475610293
WHATSAPP_ACCESS_TOKEN=EAABsbCS1iZAg...
WEBHOOK_VERIFY_TOKEN=your-custom-verify-string

SDK Usage#

If you prefer programmatic setup over the config file, register the integration directly with the SDK:

index.ts
import { Cognest } from '@cognest/sdk'
import { WhatsApp } from '@cognest/integrations/whatsapp'

const cognest = new Cognest({ configPath: false })

cognest.use(WhatsApp, {
  phoneNumberId: process.env.WHATSAPP_PHONE_ID!,
  accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,
  webhookVerifyToken: process.env.WEBHOOK_VERIFY_TOKEN!,
  settings: {
    apiVersion: 'v19.0',
    webhookPath: '/webhooks/whatsapp',
    messageTimeout: 30_000,
    retryAttempts: 3,
    readReceipts: true,
  },
})

await cognest.start()

Available Methods#

Access WhatsApp methods through cognest.integration('whatsapp'):

MethodParametersDescription
`send(to, body, options?)``to`: phone number (E.164), `body`: message textSend a plain text message to a recipient
`sendTemplate(to, templateName, components?)``to`: phone number, `templateName`: approved template IDSend a pre-approved message template with optional dynamic components
`sendMedia(to, media, options?)``to`: phone number, `media`: `{ type, url | id, caption? }`Send an image, video, document, or audio file
`markAsRead(messageId)``messageId`: the WhatsApp message IDMark a received message as read (sends blue checkmarks)
`getProfile(phoneNumber)``phoneNumber`: E.164 formatted numberRetrieve the contact's WhatsApp profile name and status

Events#

Subscribe to events emitted by the WhatsApp integration. All event handlers receive a typed context object:

EventPayloadDescription
`message``{ from, body, messageId, timestamp, context? }`Incoming text message from a user
`message:image``{ from, imageId, mimeType, caption?, sha256 }`Incoming image attachment
`message:location``{ from, latitude, longitude, name?, address? }`Shared location from a user
`message:reaction``{ from, messageId, emoji }`Reaction added to a message
`status:delivered``{ messageId, recipientId, timestamp }`Message was delivered to the recipient's device
`status:read``{ messageId, recipientId, timestamp }`Message was read by the recipient

Example#

A complete example that listens for incoming messages, processes them with an AI engine, and replies:

bot.ts
import { Cognest } from '@cognest/sdk'
import { WhatsApp } from '@cognest/integrations/whatsapp'

const cognest = new Cognest()
const wa = cognest.integration<WhatsApp>('whatsapp')

// Handle incoming text messages
cognest.on('whatsapp:message', async (ctx) => {
  const { from, body, messageId } = ctx.payload

  // Mark as read immediately
  await wa.markAsRead(messageId)

  // Skip if message is empty or a status broadcast
  if (!body?.trim() || from === 'status@broadcast') return

  // Process with AI engine
  const response = await cognest.engine.chat({
    conversationId: `wa:${from}`,
    message: body,
    metadata: {
      channel: 'whatsapp',
      sender: from,
    },
  })

  // Reply to the user
  await wa.send(from, response.text, {
    previewUrl: true,
  })
})

// Handle image messages
cognest.on('whatsapp:message:image', async (ctx) => {
  const { from, imageId, caption } = ctx.payload

  await wa.send(from, 'Thanks for the image! Let me take a look.')

  // Download and process the image
  const imageBuffer = await wa.downloadMedia(imageId)

  const analysis = await cognest.engine.vision({
    image: imageBuffer,
    prompt: caption || 'Describe this image.',
  })

  await wa.send(from, analysis.text)
})

// Track delivery status
cognest.on('whatsapp:status:delivered', async (ctx) => {
  console.log(
    `Message ${ctx.payload.messageId} delivered to ${ctx.payload.recipientId}`
  )
})

// Send a template message for outbound campaigns
async function sendWelcome(phoneNumber: string, name: string) {
  await wa.sendTemplate(phoneNumber, 'welcome_message', [
    {
      type: 'body',
      parameters: [{ type: 'text', text: name }],
    },
  ])
}

await cognest.start()
console.log('WhatsApp bot is running')

Troubleshooting#

Common Issues

Webhook verification fails: Ensure your WEBHOOK_VERIFY_TOKEN matches exactly what you configured in the Meta Developer Portal webhook settings. Messages not sending: Check that the recipient has opted in and the 24-hour conversation window is open. For conversations outside the window, use sendTemplate() with an approved template. 403 Forbidden errors: Your access token may have expired. Generate a new permanent token from the System Users section in Meta Business Settings. Media download fails: Media URLs expire after a short period. Download media immediately when the message:image event fires rather than deferring it.