DEV Community

Anthony
Anthony

Posted on

Bypass WhatsApp Template Limits with a Button-Triggered Formatting Flow

Summary

WhatsApp imposes a 24-hour customer-service window during which you can send any message; outside of it, only template messages (unformatted) are allowed. Goalmatic.io needs to let users send richly formatted content (articles, poems, etc.), so we built a two-step workaround:

  1. Template Send: Strip formatting and send via a WhatsApp template.
  2. On-Click Upgrade: Include a “Get Formatted Text” button in the template. When the user clicks, the 24-hour window reopens, allowing us to send the original, fully formatted message.

Below is an end-to-end example in Node.js using the WhatsApp Cloud API.


The Challenge

WhatsApp’s customer-service window rules mean:

  • Within 24 hours of a user message, you can send any content (text, media, formatted).
  • After 24 hours, only template messages (unformatted, pre-approved) are allowed.

But Goalmatic users want formatting—bold, italics, lists, links—to enrich their content. If you try to include Markdown or HTML in a template payload, WhatsApp returns a 400 error. How do we let users see formatted content when they click outside the 24-hour window?


🛠️ Two-Part Workaround

  1. Send the Template Strip all Markdown/HTML. Use a generic “Your content is here” template and include a button:
type dataType = {
  message: string;
  recipientNumber: string;
  uniqueTemplateMessageId: string;
}

export const goalmatic_whatsapp_workflow_template = (data: dataType) => {
  return JSON.stringify({
    'messaging_product': 'whatsapp',
    'recipient_type': 'individual',
    'to': data.recipientNumber,
    'type': 'template',
    'template': {
      'name': 'workflow_template',
      'language': {
        'code': 'en',
      },
      'components': [
        {
          'type': 'header',
          'parameters': [
            {
              'type': 'image',
              'image': {
                'link': 'https://goalmatic.io/hero/workflow.png',
              },
            },
          ],
        },
        {
          'type': 'body',
          'parameters': [
            {
              'type': 'text',
              'text': data.message,
            },

          ],
        },
        {
          'type': 'button',
          "index": "0",
          "sub_type": "quick_reply",
          'parameters': [
            {
              "type": "payload",
              "payload": data.uniqueTemplateMessageId
            }
          ],
        },
      ],
    },
  })
}
Enter fullscreen mode Exit fullscreen mode
  1. Handle the Click When the user taps “Get Formatted Text,” WhatsApp opens a conversation thread (re-opening the 24-hour window). Your webhook sees this click (or an inbound URL request) and responds with the full formatted content using a standard text API call.

Code Snippet: Stripping Formatting & Sending Both Messages

import express from 'express';
import bodyParser from 'body-parser';
import fetch from 'node-fetch';

// Helpers

/**
 * Strip markdown/HTML tags to produce unformatted text
 * @param {string} input
 * @returns {string}
 */
function stripFormatting(input) {
  // Simple removal of markdown/HTML; adjust regex as needed
  return input
    .replace(/<\/?[^>]+(>|$)/g, '')    // remove HTML tags
    .replace(/(\*|_|~|`){1,3}/g, '')    // remove markdown **, __, ~~ , `
    .trim();
}

/**
 * Send a WhatsApp Cloud API message
 * @param {string} payload
 */
async function sendWhatsApp(payload) {
  const token = process.env.WA_CLOUD_TOKEN;
  const phoneNumberId = process.env.WA_PHONE_NUMBER_ID;

  const res = await fetch(
    `https://graph.facebook.com/v15.0/${phoneNumberId}/messages`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(payload)
    }
  );
  if (!res.ok) {
    const err = await res.text();
    console.error('WhatsApp API error:', err);
  }
}

// Express App

const app = express();
app.use(bodyParser.json());

app.post('/send', async (req, res) => {
  const { to, formattedText } = req.body;
  const unformattedText = stripFormatting(formattedText);

  // 1️⃣ Send unformatted template with button
  await sendWhatsApp({
    to,
    type: 'template',
    template: {
      name: 'get_formatted_text',
      language: { code: 'en_US' },
      components: [
        {
          type: 'BODY',
          parameters: [{ type: 'text', text: unformattedText }]
        },
        {
          type: 'BUTTONS',
          buttons: [
            {
              type: 'url',
              url: `https://your-domain.com/fetch?to=${encodeURIComponent(to)}&msg=${encodeURIComponent(formattedText)}`,
              title: 'Get Formatted Text'
            }
          ]
        }
      ]
    }
  });

  res.sendStatus(200);
});

app.get('/fetch', async (req, res) => {
  // 2️⃣ User clicked button; send formatted text
  const { to, msg } = req.query;

  await sendWhatsApp({
    to,
    type: 'text',
    text: { body: msg }
  });

  res.send('<h1>Formatted message sent! ✅</h1>');
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`🚀 Server running on port ${PORT}`));
Enter fullscreen mode Exit fullscreen mode

How It Works

  1. stripFormatting removes Markdown and HTML so the template body is plain text.
  2. POST /send
  • Receives the user’s destination number and full formatted text.
  • Sends a WhatsApp template message with a URL-button “Get Formatted Text.”

    1. GET /fetch
  • Triggered when the user clicks the button (opening the 24-hour window).

  • Sends the original formattedText back as a non-template text message, preserving all formatting.


Next Steps & Tips

  • Template Approval: You must pre-approve your “get_formatted_text” template with Facebook Business Manager.
  • Security: Sign or encrypt the URL parameters so users can’t tamper with message text.
  • Rich Media: You can adapt this pattern to send images, documents, or even interactive lists once the window is open.
  • UX: Customize the “Get Formatted Text” button title or use a quick-reply button to keep users inside WhatsApp.

Have you built something similar or run into other messaging-platform quirks? Let me know in the comments!

Top comments (0)