Skip to content

feat: add WhatsApp Business Cloud API adapter#102

Open
ghellach wants to merge 24 commits intovercel:mainfrom
ghellach:feat/adapter-whatsapp
Open

feat: add WhatsApp Business Cloud API adapter#102
ghellach wants to merge 24 commits intovercel:mainfrom
ghellach:feat/adapter-whatsapp

Conversation

@ghellach
Copy link

@ghellach ghellach commented Feb 25, 2026

Summary

  • Add @chat-adapter/whatsapp package implementing the WhatsApp Business Cloud API adapter using the Meta Graph API (v21.0, configurable via apiVersion option)
  • Support for sending/receiving text messages, reactions (add/remove), interactive reply buttons (max 3), typing indicators, read receipts, and DMs
  • Webhook verification via GET challenge-response and POST HMAC-SHA256 signature validation
  • Card rendering with interactive buttons when possible, falling back to formatted text
  • Media download support for images, documents, audio, video, voice messages, and stickers via downloadMedia() with lazy fetchData() on attachments
  • Location message support with structured text and Google Maps URLs (with lat/lng validation)
  • Voice message support (separate from audio)
  • Legacy button response handling (template quick replies)
  • Callback data encoding/decoding for interactive reply round-trips
  • Long messages automatically split into multiple messages at paragraph/line boundaries (respecting WhatsApp's 4096-char limit)
  • Escaped formatting character preservation in markdown conversion
  • All WhatsApp DMs treated as mentions (isMention: true) for correct SDK routing
  • Full test suite (78 tests) covering adapter logic, media attachments, card rendering, message splitting, and markdown conversion
  • Documentation and configuration updates across README, CLAUDE.md, turbo.json, vitest workspace, and docs site

Details

Capabilities

Feature Support
Post message Yes (auto-splits long messages)
Edit message No (throws — WhatsApp limitation)
Delete message No (throws — WhatsApp limitation)
Reactions Yes (add and remove)
Typing indicator Yes
DMs Yes (all WhatsApp conversations are 1:1)
Cards/Buttons Partial (max 3 reply buttons, text fallback otherwise)
Media download Yes (images, documents, audio, video, voice, stickers)
Location Yes (with Google Maps URL, lat/lng validated)
Streaming No
Message history No (not exposed by Cloud API)

Media support

Inbound media messages (images, documents, audio, video, voice, stickers) are exposed as Attachment objects on the Message with:

  • type"image", "file", "audio", "video"
  • mimeType — from WhatsApp webhook payload
  • fetchData() — lazy download via two-step Graph API (get URL, then fetch binary)

Location messages include a Google Maps URL and structured text with name/address/coordinates. Latitude and longitude are validated with Number.isFinite() before URL construction.

Thread ID format

whatsapp:{phoneNumberId}:{userWaId}

Environment variables

  • WHATSAPP_ACCESS_TOKEN — Meta Graph API access token
  • WHATSAPP_APP_SECRET — App secret for webhook signature verification
  • WHATSAPP_PHONE_NUMBER_ID — Phone number ID for sending messages
  • WHATSAPP_VERIFY_TOKEN — Token for webhook URL verification

Test plan

  • Unit tests pass for adapter core (encode/decode thread IDs, webhook verification, message parsing)
  • Unit tests pass for media attachments (image, document, audio, video, voice, sticker, location)
  • Unit tests pass for isMention routing
  • Unit tests pass for card rendering (interactive buttons, text fallback, truncation, callback data)
  • Unit tests pass for message splitting (paragraph breaks, line breaks, hard breaks, content preservation)
  • Unit tests pass for markdown conversion (WhatsApp *bold*/~strike~ ↔ standard markdown, escaped chars)
  • pnpm typecheck passes across all packages
  • pnpm check (lint/format) passes
  • pnpm knip (unused exports/deps) passes
  • pnpm test passes (78 WhatsApp adapter tests + all existing tests)
  • Manual testing with WhatsApp Business test account (text, images, reactions, conversations)

Add @chat-adapter/whatsapp with support for sending/receiving messages,
reactions, interactive reply buttons, typing indicators, and webhook
verification via the Meta Graph API. Includes full test suite,
documentation updates, and workspace/turbo configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link
Contributor

vercel bot commented Feb 25, 2026

@ghellach is attempting to deploy a commit to the Vercel Team on Vercel.

A member of the Team first needs to authorize it.

…pp adapter

- Add downloadMedia() public method for fetching images, documents,
  audio, video, and stickers via the Graph API (two-step: URL then binary)
- Populate message attachments with lazy fetchData() for all media types
- Add location support with Google Maps URL and structured text
- Add audio, video, sticker, and location fields to WhatsAppInboundMessage
- Set isMention: true on all messages (WhatsApp DMs are always direct)
- Update parseMessage to include attachments and isMention
- Add 10 new tests covering all media types, locations, and isMention
- Update docs feature matrix to reflect media receive support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ghellach ghellach marked this pull request as ready for review February 25, 2026 03:34
Copilot AI review requested due to automatic review settings February 25, 2026 03:34
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new @chat-adapter/whatsapp package that integrates the Chat SDK with the WhatsApp Business Cloud API (Meta Graph API), plus the necessary monorepo wiring (tests, env vars, docs).

Changes:

  • Introduces packages/adapter-whatsapp with adapter implementation, card rendering, markdown conversion, and a dedicated test suite.
  • Wires the new package into the workspace tooling (Vitest workspace, Turbo env passthrough, lockfile).
  • Updates docs/README/CLAUDE.md to include WhatsApp as a supported platform and document its package.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
vitest.workspace.ts Adds the WhatsApp adapter package to the Vitest workspace.
turbo.json Adds WhatsApp-related environment variables to Turbo’s global env passthrough list.
pnpm-lock.yaml Adds the new package importer and updates dependency snapshots accordingly.
packages/adapter-whatsapp/vitest.config.ts Defines Vitest config/coverage settings for the new adapter package.
packages/adapter-whatsapp/tsup.config.ts Adds build configuration for bundling the adapter.
packages/adapter-whatsapp/tsconfig.json Adds TypeScript configuration for the new package.
packages/adapter-whatsapp/src/types.ts Introduces WhatsApp webhook/media/type definitions used by the adapter.
packages/adapter-whatsapp/src/markdown.ts Implements WhatsApp-specific markdown conversion via AST conversion.
packages/adapter-whatsapp/src/markdown.test.ts Tests markdown conversion behavior.
packages/adapter-whatsapp/src/index.ts Implements the WhatsApp adapter: webhook handling, send/reaction/typing/read, thread ID encode/decode, media download, message parsing.
packages/adapter-whatsapp/src/index.test.ts Tests thread ID logic, parsing behavior, attachments, and webhook verification challenge.
packages/adapter-whatsapp/src/cards.ts Converts Card elements into WhatsApp interactive payloads or text fallback.
packages/adapter-whatsapp/src/cards.test.ts Tests card conversion and fallback logic.
packages/adapter-whatsapp/package.json Adds the new package manifest, scripts, and dependencies.
apps/docs/content/docs/index.mdx Adds WhatsApp to the platform list and package table in docs landing page.
apps/docs/content/docs/adapters/index.mdx Adds WhatsApp to the adapters overview matrices and adapter list.
README.md Adds WhatsApp to the supported platforms and adapter list.
CLAUDE.md Adds WhatsApp adapter package and env vars to repo documentation.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

ghellach and others added 2 commits February 24, 2026 22:46
- Validate Graph API response before accessing messages[0].id in
  sendTextMessage and sendInteractiveMessage
- Escape backticks and backslashes in escapeWhatsApp()
- Apply escapeWhatsApp() to renderText() content in all style branches
- Use webhook phoneNumberId in buildMessage() instead of this.phoneNumberId
- Encode proper threadId in parseMessage() instead of empty string
- Strict decodeThreadId() validation (exactly 2 segments after prefix)
- Add tests for extra segments in decodeThreadId and threadId in parseMessage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
haydenbleasel and others added 2 commits March 6, 2026 15:47
Bring over several enhancements from chitru's WhatsApp adapter PR (vercel#179):

- Voice message support (separate from audio)
- Legacy button response handling (template quick replies)
- Callback data encoding/decoding for interactive reply round-trips
- Message truncation at WhatsApp's 4096 char limit
- Example app integration (adapters, webhook route, package.json)
- GET webhook forwarding for WhatsApp verification challenges
- Package README and changeset
- Tests for all new functionality (68 total)

Co-Authored-By: Chitru Shrestha <chitra.shrestha@akuru.com.au>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	README.md
#	apps/docs/content/docs/adapters/index.mdx
#	apps/docs/content/docs/index.mdx
#	examples/nextjs-chat/src/lib/adapters.ts
#	pnpm-lock.yaml
#	vitest.workspace.ts
@haydenbleasel
Copy link
Member

Nice one @ghellach - jumping in to assist 👍

Wrap handleInboundMessage calls in try/catch to log errors if
synchronous processing fails (e.g., thread ID encoding). The async
processing already has its own error handling in Chat.processMessage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link
Contributor

vercel bot commented Mar 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview, Comment, Open in v0 Mar 7, 2026 0:25am
chat-sdk-nextjs-chat Ready Ready Preview, Comment, Open in v0 Mar 7, 2026 0:25am

haydenbleasel and others added 2 commits March 6, 2026 15:55
Use [^\n*] and [^\n~] in fromWhatsAppFormat regex to prevent bold/strike
spans from merging across line boundaries. Adds a regression test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the untyped `object` parameter in sendInteractiveMessage with
the proper WhatsAppInteractiveMessage type for full type safety.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the emoji name-to-unicode mapping out of resolveEmoji() so it is
not re-allocated on every call.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
haydenbleasel and others added 4 commits March 6, 2026 15:56
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The adapter supports typing indicators but the method was missing from
the recording proxy list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix line-length formatting in markdown.ts regex and add
@chat-adapter/whatsapp to the valid packages list in readme tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the hand-rolled EMOJI_MAP with the shared defaultEmojiResolver
from the chat SDK. WhatsApp uses unicode emoji like GChat, so toGChat()
provides the correct mapping with broader coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
haydenbleasel and others added 3 commits March 6, 2026 16:03
Add apiVersion option to WhatsAppAdapterConfig (defaults to v21.0)
so users can upgrade without waiting for a package release.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Coerce and validate latitude/longitude with Number.isFinite() to
prevent unexpected URL construction from malformed webhook payloads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…essage

Callers expecting an edit would get duplicate messages with the silent
fallback. Throwing makes the unsupported operation explicit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
haydenbleasel and others added 4 commits March 6, 2026 16:03
…atsApp

Explain why toWhatsAppFormat doesn't need newline guards like
fromWhatsAppFormat does — the standard markdown parser output
never produces spans crossing line boundaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add comments explaining that non-prefixed and malformed callback data
is intentionally passed through for legacy/external button IDs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Include all adapter methods in the recording list for complete
debugging traces, even for unsupported operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Escaped asterisks and tildes in standard markdown (e.g. \* and \~) are
now preserved through the conversion pipeline so WhatsApp renders them
as literal characters instead of misinterpreting them as formatting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace silent truncation at 4096 chars with message splitting that
breaks on paragraph (\n\n) then line (\n) boundaries, sending multiple
messages so no content is lost. Adds 8 tests for the splitting logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
haydenbleasel and others added 2 commits March 6, 2026 16:18
- Fix README: editMessage/deleteMessage both throw, not fallback/no-op
- Fix editMessage JSDoc to reflect it throws
- Make deleteMessage throw instead of silently warning (consistent with editMessage)
- Bump @types/node to ^25.3.2 to match monorepo
- Add sample-messages.md with webhook payload examples

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add whatsapp.mdx covering installation, usage, Meta app setup,
webhook config, interactive messages, media attachments, 24-hour
messaging window, configuration, features, and troubleshooting.
Also add WhatsApp to the adapters navigation in meta.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants