Direct answer
The API call is the easy part; the system around it is the work. Sending clinic messages over the WhatsApp Cloud API for many clinics from one SaaS comes down to a sender model with a safe fallback, utility-only templates in each language you support, a queue with dead-letter handling, platform-enforced guardrails and atomic quotas, and webhooks verified against the raw request.
The API call is the easy part. Sending one appointment reminder through Meta’s WhatsApp Cloud API is a single authenticated request; sending reminders, prescriptions, bills and recalls for many clinics from one SaaS is a systems problem. Building this into Clinizy Care taught us that five decisions carry the weight: which number messages come from, keeping every template in the utility category, putting a queue between the app and Meta, enforcing guardrails so no patient gets spammed, and verifying webhooks properly.
Clinizy Care is the hospital management software we build and run for clinics, nursing homes and small hospitals in India (the builder’s story of Clinizy Care covers the wider product). Its WhatsApp for clinics feature sends appointment reminders, prescriptions, bills, lab reports and recalls. We integrate the Meta WhatsApp Cloud API directly on Graph API v20.0, with no business solution provider in the middle of live sending.
Key takeaways
- Support two sender models. A shared platform number by default, the clinic’s own WhatsApp Business Account as an option, and a fallback when a clinic’s sender is misconfigured.
- Keep templates utility. Meta treats mixed utility-and-promo templates as marketing, and category drives what you pay.
- Never send inline. Queue external channels with a dead-letter queue and idempotent workers, and log the provider’s full error body.
- Guardrails belong to the platform. Quiet hours, opt-outs, daily caps and licence checks run before any send.
- Reserve quota before sending, and test webhooks through your real middleware stack.
Should a multi-clinic SaaS use one WhatsApp number or one per clinic?
Support both, and make the shared number the default. In Clinizy Care, messages go out from one shared platform number unless a clinic connects its own WhatsApp Business Account, in which case its messages go out through its own sender. The shared number is the lowest-friction start; a clinic’s own account gives it its own sender identity and its own standing with Meta.
The trade-off is mostly about shared limits. According to Meta’s messaging limits documentation, limits are set at the business portfolio level and shared by every phone number in that portfolio, and Meta’s throughput guide sets a default of 80 messages per second per business phone number. On a shared sender, every clinic draws on the same pool and the same quality signal, so guardrails have to be enforced by the platform, not left to each clinic.
| Aspect | Shared platform number (default) | Clinic’s own WhatsApp Business Account |
|---|---|---|
| Sender | One number operated by the platform | The clinic’s own number |
| Credentials | Managed centrally | Clinic’s access token, encrypted at rest with AES-256-GCM |
| Meta limits | Shared by every clinic on the number | The clinic’s own portfolio limits |
| Failure mode | A problem affects every clinic on the number | Misconfiguration falls back to the platform number |
Two details matter more than they look. A clinic’s access token can send messages in that clinic’s name, so we encrypt it at rest with AES-256-GCM. And own-number setups can break: a token is rotated, a setting changes in Meta’s dashboard. When a clinic’s own sender is misconfigured, Clinizy Care falls back to the platform number rather than failing silently. A reminder from an unexpected number is a minor annoyance; a reminder that never arrives is a missed appointment nobody noticed.
Which WhatsApp template category should clinic messages use?
Utility. Every template must be categorised as authentication, marketing or utility, and templates are the only message type you can send outside a customer service window, according to Meta’s template documentation. Meta’s template categorization guide defines utility templates as non-promotional messages that are specific to the user or essential to them, which is where transactional clinic messages belong. All of Clinizy Care’s templates are in the utility category.
The discipline is in keeping them there. The same guide treats templates with mixed content as marketing; one of its examples is an order update with a promo. For a clinic, that means no discount line tucked under a bill.
Language is the other design decision. Clinizy Care has 29 WhatsApp templates in total, covering 15 message types in English and Hindi, created and approved in Meta’s WhatsApp Manager. Each clinic chooses its WhatsApp language, English by default, and printed documents also come in English and Hindi. Meta’s template documentation says it does not translate template strings or variables, so each language version is a separate template to write, submit and keep in sync.
Category also drives cost. According to Meta’s WhatsApp Business Platform pricing page, Meta has charged per message since July 1, 2025, replacing conversation-based pricing, with rates that vary by template category and the recipient’s country calling code. Utility templates delivered within an open customer service window are free, and that window opens for 24 hours when the patient messages the business. A reminder sent the evening before an appointment will often land outside any open window, so budget for it as a paid utility message.
How do you send WhatsApp messages reliably from a multi-tenant SaaS?
Never call Meta from inside the request that triggered the message. When something happens in Clinizy Care, such as an appointment booked or a bill finalised, the dispatch layer writes the in-app notification directly and puts every external channel (WhatsApp, SMS, email) onto AWS SQS queues. A worker picks messages up and talks to Meta, so a slow provider becomes a queue-depth problem instead of an error on someone’s screen.
- Dead-letter queue after three attempts. SQS redrives a message to a dead-letter queue after 3 failed receives, so one broken message can’t retry forever.
- Exponential backoff. If the worker hits poll errors, it backs off exponentially instead of hammering the queue.
- Idempotent processing. AWS’s SQS at-least-once delivery documentation warns that you might receive a message again and tells you to design for idempotency. Our worker processing is idempotent, with a backstop for poison messages.
- Bounded concurrency for campaigns. Campaign enqueueing runs 10 at a time, so a long recall list goes onto the queue at a controlled pace.
The queue also absorbs Meta’s back-pressure. Meta’s throughput guide says the API returns error 130429 when you exceed your throughput level, and Meta’s error code reference lists 131056 for too many messages to the same recipient in a short period. Both mean: slow down and retry later.
The error message that hid the real error
By default, axios, the HTTP client we use, rejects any non-2xx response, and, as its README shows, the server’s body sits on error.response.data rather than in the error message you’d normally log. Meta puts the real explanation in that body, so we now fold Meta’s error details into each message’s failure reason. That is how we tracked down error 131008, which Meta’s error code reference describes as “The request is missing a required parameter.” Ours came from a URL-button parameter in a template. Without Meta’s details, a failed send just looks like a failed send.
Want to build this — the right way?
Brynex Labs designs and ships production-grade AI agents, automation, and software for teams in India and worldwide. Book a free scoping call and we'll tell you honestly what's worth building — and what isn't yet.
How do you stop a SaaS from spamming patients on WhatsApp?
Put the rules in the platform, in front of every send. Meta’s opt-in guidance says businesses must obtain opt-in before messaging people and must honour opt-out requests, and notes that WhatsApp rate-limits businesses with sustained low quality ratings. In Clinizy Care, every WhatsApp message is checked against what we call the governance chain before it is sent:
- Quiet hours. A routine reminder is never worth waking a patient for.
- Per-clinic opt-out list. Matching works across phone formats (+91…, 91… and bare 10-digit numbers), because Indian numbers turn up in all three, and an opt-out that only matches one spelling isn’t an opt-out.
- Per-patient, per-message-type daily cap. For example, at most 2 appointment reminders per patient per day, while critical lab alerts get a much higher ceiling. A guardrail should never block the message that matters most.
- Licence and kill-switch eligibility. Only licensed clinics can send on paid channels like WhatsApp and SMS.
The chain applies whether the message is a one-off prescription, an automated recall or a bulk campaign, and much of the sending is automated. Many of Clinizy Care’s 24 built-in clinic automations deliver over WhatsApp: follow-up recalls, no-show win-back, refill reminders, vaccine reminders, feedback requests, dues recovery and instant lab report delivery. A logic bug in an automation can message the same patient again and again; a cap enforced at the platform layer catches that whichever automation misbehaved. Consent is also a data-protection question, which we cover in the DPDP Act for health-tech builders.
How do Meta’s limits and your own SaaS quotas fit together?
Treat them as separate layers. Meta limits how many people, and how fast, a portfolio or number can message; your product decides how much each tenant may use.
| Limit | Set by | Scope | What it caps |
|---|---|---|---|
| Messaging limit | Meta | Business portfolio | Unique users messaged outside a customer service window in a moving 24 hours: 250 for new portfolios, scaling through 2,000, 10,000 and 100,000 to unlimited |
| Throughput | Meta | Business phone number | 80 messages per second by default, up to 1,000 by automatic upgrade (error 130429) |
| Pair rate limit | Meta | One sender to one recipient | Too many messages to the same user in a short period (error 131056) |
| Plan quota | Clinizy Care | Clinic | Monthly messages on the clinic’s plan, alerts at 90% and 100% |
| Daily cap | Clinizy Care | Patient and message type | For example, 2 appointment reminders per day |
Meta’s figures come from its messaging limits and throughput pages; the 1,000-per-second upgrade has conditions, including an unlimited messaging limit.
On our side, the detail that matters is that quota is reserved atomically before sending, not counted after. Otherwise a campaign and the day’s reminders running at the same time could each read “quota remaining” and together overspend it. Reserving first turns the race into a simple rule: if the reservation fails, the send doesn’t happen. Paid top-ups go through an idempotent payment ledger, so a retried payment callback can’t credit the same top-up twice. Clinizy Care plans start at ₹1,999/month excluding GST, with the WhatsApp Business API listed as an add-on. Ledgers got their own post: what we learned building a GST billing engine for clinics.
Want to build this — the right way?
Brynex Labs designs and ships production-grade AI agents, automation, and software for teams in India and worldwide. Book a free scoping call and we'll tell you honestly what's worth building — and what isn't yet.
How do you verify WhatsApp Cloud API webhooks?
Two kinds of request hit the same endpoint, and each has a trap. According to Meta’s webhooks getting-started guide, setup starts with a GET request carrying hub.mode (always “subscribe”), hub.verify_token (the string you set in the App Dashboard) and hub.challenge; your endpoint checks the token and responds with the challenge. Event notifications arrive as POSTs signed with a SHA256 signature in the X-Hub-Signature-256 header, which you recompute from the payload and your App Secret.
Our GET handshake failed with a 403. The cause was express-mongo-sanitize, a request-sanitising middleware that, according to its README, targets keys that begin with $ or contain a ., because MongoDB reserves them for operators. hub.mode and hub.verify_token contain dots, so they were stripped before our handler saw them and Meta’s verification was rejected. The fix was to read the handshake from the raw URL. The lesson: middleware that rewrites input will eventually rewrite something a third party depends on, so test provider handshakes through your full stack.
The POST side follows four rules: compute the HMAC over the raw body, not re-serialised JSON; compare with a timing-safe function; fail closed on a missing or wrong signature; and acknowledge with 200 immediately, then process. In Clinizy Care, delivery statuses (sent, delivered, read, failed) then update the message log. Meta’s guide says failed deliveries are retried over the following 36 hours, so make status updates safe to apply twice.
// Simplified sketch, not production code
app.post('/webhooks/whatsapp', express.raw({ type: 'application/json' }), (req, res) => {
const received = Buffer.from(req.get('X-Hub-Signature-256') || '');
const expected = Buffer.from('sha256=' + crypto
.createHmac('sha256', process.env.META_APP_SECRET)
.update(req.body) // the raw bytes Meta signed
.digest('hex'));
if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {
return res.sendStatus(401); // fail closed
}
res.sendStatus(200); // acknowledge first
setImmediate(() => applyStatusUpdates(JSON.parse(req.body))); // then process
});
What would we build next?
Automatic handling of STOP-style replies. Today, opt-outs are managed per clinic and enforced on every send, but a patient’s reply doesn’t yet update the list by itself. Closing that loop means recognising opt-out replies in English and Hindi, working out which clinic a reply belongs to when the original went out from the shared number, and reusing the phone-format matching the governance chain already has.
The honest takeaway
WhatsApp at clinic scale is less an API integration than a small messaging platform: a sender model with a safe fallback, strictly transactional templates, a queue that absorbs failure, guardrails that protect patients from your own automations, quotas that can’t be overspent, and webhooks you can trust. None of it is exotic; it just has to exist before the first busy Monday. The same foundations run through the rest of the product, covered in how we built a multi-tenant HMS for Indian clinics and the Clinizy Care case study.
We build this kind of messaging automation for clients too, through our AI agents and automation practice, with the surrounding product built by our AI-native software engineering team. Building for clinics or hospitals? See our healthcare software engineering page.
Technologies Covered
Written by
Abhi PandeyFounder & CTO
Abhi Pandey is the Founder & CTO of Brynex Labs. He leads product and engineering for Clinizy Care, the hospital management software Brynex Labs builds and runs for India's clinics and nursing homes, and for the AI agents and SaaS platforms the studio builds for clients. He writes about multi-tenant SaaS architecture, applied AI engineering, and shipping reliable systems to production.
Related Services
Agentic AI & Intelligent Automation
Autonomous AI agents that reason, use tools, and execute complex workflows end-to-end — built on LangChain, LangGraph, RAG, and your own business data.
Explore serviceAI-Native Software Engineering
Full-cycle product engineering — custom software, SaaS platforms, web & mobile apps, cloud infrastructure, and legacy modernization — built AI-first for speed and scale.
Explore serviceRead Next
How We Built a Multi-Tenant HMS for Indian Clinics
We run Clinizy Care on one shared MongoDB database with shared collections: every tenant-owned document carries a tenantId, and a Mongoose plugin injects that filter into every read, write, count and aggregation, failing closed when no tenant is in context. At ₹1,999 a month for a solo clinic, a database per tenant would cost and operate out of proportion; a strict, tested guard with tenant-first indexes fits the economics.
The DPDP Act for Health-Tech Builders: What We Built, What's Next
For health-tech builders, the DPDP Act, 2023 and the DPDP Rules, 2025 turn privacy into a product spec: itemised notices, consent as easy to withdraw as to give, minimum security safeguards with logs kept for a year, a detailed breach report to the Board within 72 hours, and working access, correction and erasure requests. The core obligations commence in May 2027. In Clinizy Care we have built India hosting, tenant isolation, role-based access, two-factor sign-in, an append-only audit trail and clinic-level export and deletion; patient-level consent, individual rights requests, field-level encryption and read auditing are next.
Lessons From Building a GST Billing Engine for Clinics
Most GST billing bugs are data bugs that surface as tax errors. Store money as integer paise, attach tax rates to the product and service master, decide CGST/SGST versus IGST in one place, back-calculate GST from MRP, apply discounts before tax, compute dates in IST, and make finalized bills immutable.