Skip to content

feat(cloud-api): support CTA URL buttons in sendButtons#2634

Open
iscarelli wants to merge 2 commits into
evolution-foundation:developfrom
iscarelli:feat/cloud-api-cta-url-button
Open

feat(cloud-api): support CTA URL buttons in sendButtons#2634
iscarelli wants to merge 2 commits into
evolution-foundation:developfrom
iscarelli:feat/cloud-api-cta-url-button

Conversation

@iscarelli

@iscarelli iscarelli commented Jul 11, 2026

Copy link
Copy Markdown

What

Add support for CTA URL (link) buttons in sendButtons on the Cloud API (WhatsApp Business) channel.

Why

On the Cloud API channel, buttonMessage mapped every button to type: 'reply', so sending a button with type: 'url' failed at Meta with:

interactive.action.buttons.0.reply is required: id  (code 100, OAuthException)

This made it impossible to send a link button in a free-form (session) message, even though the Cloud API supports it through the interactive cta_url type. Reported in #1249.

How

  • buttonMessage now preserves type: 'url' buttons (as a cta_url entry) instead of forcing them into the reply shape.
  • sendMessageWithTyping detects a cta_url button and builds an interactive cta_url message:
    "interactive": {
      "type": "cta_url",
      "body": { "text": "<title>" },
      "action": { "name": "cta_url", "parameters": { "display_text": "<text>", "url": "<url>" } }
    }
    Per the Cloud API, cta_url allows a single link button and cannot be combined with reply buttons, so the first url button wins.
  • The reply-button path is unchanged.
  • The internal text echo falls back to the button displayText, so cta_url buttons no longer render as undefined.

Request example

POST /message/sendButtons/{instance}
{
  "number": "5599999999999",
  "title": "Your gallery is ready!",
  "buttons": [
    { "type": "url", "displayText": "Open gallery", "url": "https://example.com/g/abc" }
  ]
}

Testing

Tested against a live WhatsApp Business (Cloud API) number: the button is accepted by Meta (returns a wamid) and renders as a tappable link button inside the 24h session window. Existing reply-button sends are unaffected.

Closes #1249

Summary by Sourcery

Add support for CTA URL buttons in WhatsApp Cloud API sendButtons so link buttons can be sent as interactive messages in session conversations.

New Features:

  • Allow sending CTA URL (link) buttons via sendButtons on the WhatsApp Cloud API channel by mapping URL buttons to interactive cta_url messages.

Enhancements:

  • Preserve URL-type buttons instead of coercing them into reply buttons in buttonMessage for the Cloud API channel.
  • Improve internal text echo for buttons by falling back to the button displayText when reply title is absent, avoiding undefined labels.

On the Cloud API (WhatsApp Business) channel, sendButtons hardcoded every
button to type "reply", so a button with type "url" failed with
"interactive.action.buttons.0.reply is required: id" (code 100). This made it
impossible to send a link (CTA URL) button in a free-form session message,
even though the Cloud API supports it via the interactive "cta_url" type.

This preserves url buttons through buttonMessage and, when a cta_url button is
present, builds an interactive cta_url message
(action.name = "cta_url", parameters = { display_text, url }) instead of the
reply-button payload. Per the Cloud API, cta_url allows a single link button and
cannot be combined with reply buttons, so the first url button wins.

The reply-button path is unchanged. The internal text echo now falls back to the
button displayText so cta_url buttons no longer render as "undefined".

Closes evolution-foundation#1249
@sourcery-ai

sourcery-ai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Support CTA URL (link) buttons in Cloud API WhatsApp sendButtons by mapping URL buttons to WhatsApp interactive cta_url messages and preserving their display text in internal echoes, while keeping existing reply-button behavior unchanged.

Sequence diagram for WhatsApp Cloud API sendButtons CTA URL handling

sequenceDiagram
  participant Client
  participant BusinessStartupService
  participant MetaCloudAPI

  Client->>BusinessStartupService: sendMessageWithTyping(message_with_buttons)
  alt [cta_url button present]
    BusinessStartupService->>BusinessStartupService: build cta_url interactive content
    BusinessStartupService->>MetaCloudAPI: post(content, messages)
    MetaCloudAPI-->>BusinessStartupService: response with wamid
  else [no cta_url button]
    BusinessStartupService->>BusinessStartupService: build reply interactive content
    BusinessStartupService->>MetaCloudAPI: post(content, messages)
    MetaCloudAPI-->>BusinessStartupService: response with wamid
  end
Loading

Flow diagram for buttonMessage URL to cta_url mapping

flowchart TD
  A[buttonMessage receives data.buttons] --> B{button.type === url?}
  B -->|Yes| C[Create button with type cta_url]
  C --> D[Set displayText and url]
  D --> F[Return mapped buttons]
  B -->|No| E[Create button with type reply and reply.title]
  E --> F[Return mapped buttons]
Loading

File-Level Changes

Change Details Files
Add CTA URL button handling in sendMessageWithTyping for WhatsApp Cloud API so URL buttons are sent as interactive cta_url messages.
  • Detect first cta_url-type button in incoming message buttons array and short-circuit to a dedicated CTA URL send path.
  • Construct WhatsApp Cloud API interactive payload with type cta_url, body text from message text (fallback to 'Select'), and action parameters using button displayText and url.
  • Apply quoted message context to the interactive payload when present.
  • Rewrite the internal conversation echo text to include the CTA URL button display text and URL for logging/debugging before sending.
src/api/integrations/channel/meta/whatsapp.business.service.ts
Map sendButtons URL-type buttons to WhatsApp Cloud API cta_url buttons instead of forcing them into reply shape, and improve button text fallback for internal echoes.
  • Transform input buttons with type 'url' into objects with type 'cta_url', carrying displayText and url properties, when building message data.
  • Leave non-URL buttons mapped to reply-type buttons as before, with id and title preserved.
  • Update formattedText construction for reply buttons to fall back to button.displayText when reply.title is absent, preventing undefined from rendering.
src/api/integrations/channel/meta/whatsapp.business.service.ts

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The quoted ? (content.context = { message_id: quoted.id }) : content; ternary in the new cta_url branch is a no-op on the false path and harder to read; consider replacing it with a simple if (quoted) { content.context = { message_id: quoted.id }; } for clarity.
  • In the cta_url mapping within sendButtons, you are assuming displayText and url are always present; if these can be user-provided, consider guarding against undefined values or falling back consistently (similar to the reply?.title ?? displayText echo).
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `quoted ? (content.context = { message_id: quoted.id }) : content;` ternary in the new cta_url branch is a no-op on the false path and harder to read; consider replacing it with a simple `if (quoted) { content.context = { message_id: quoted.id }; }` for clarity.
- In the cta_url mapping within `sendButtons`, you are assuming `displayText` and `url` are always present; if these can be user-provided, consider guarding against undefined values or falling back consistently (similar to the `reply?.title ?? displayText` echo).

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Address review: use an if for the quoted context instead of the no-op
ternary, only treat a url button as cta_url when it actually has a url,
and fall back display_text to the url when displayText is absent.
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.

1 participant