Back to blog
Chatmaid DevelopersAug 04, 2026·5 min read

The Complete WhatsApp API Quickstart for SaaS Developers (2026)

Adding WhatsApp to a SaaS product requires four components: Authentication — API key in the Authorization header A connected number — QR scan, one time Send endpoint — single POST to deliver messages Webhook endpoint — to receive incoming messages and status updates Everything else — idempotency, error handling, retries, media — builds on these four primitives. The sandbox lets you build and test the complete integration without a real WhatsApp number. When you're confident it works, flip the key and you're live. Full documentation: developers.chatmaid.net/docs Get started free: developers.chatmaid.net/signup

The Complete WhatsApp API Quickstart for SaaS Developers (2026)

Adding WhatsApp to a SaaS product is a surprisingly high-leverage move. WhatsApp messages have open rates above 90%. They arrive in an app users check dozens of times per day. For notifications, alerts, onboarding flows, and customer communication, WhatsApp outperforms email by almost every metric.

This guide covers the complete integration from scratch — authentication, sending, receiving, error handling, idempotency, and production deployment — using the Chatmaid API.

Prerequisites

  • A Chatmaid Developers account (free sandbox)
  • A server with a public HTTPS endpoint (for webhooks)
  • Basic familiarity with REST APIs and your language of choice

Part 1: Authentication

Chatmaid uses Bearer token authentication. Every request includes your API key in the Authorization header.

Authorization: Bearer sk_live_xxxxxxxxxxxx

Two key types:

  • sk_test_* — Sandbox. Simulates the full lifecycle. Never touches WhatsApp. Free.
  • sk_live_* — Production. Requires an active $7.99/month subscription.

Always use sk_test_* during development. The sandbox fires real webhooks, returns real response structures, and simulates delivery status — indistinguishable from production at the API level.

Store your key in an environment variable:

bash

export CHATMAID_API_KEY=sk_test_xxxxxxxxxxxx

Never hardcode keys in source files or commit them to version control.

Part 2: Connecting a Phone Number

A connected phone number is the sender for your messages. Connect one in the Chatmaid dashboard by clicking Add Number and scanning the QR code with the WhatsApp app.

Once connected, the number appears in your dashboard with a phoneId — the identifier you'll use in API calls.

You can connect multiple numbers. Each additional number is $2.99/month. Route different message types through different numbers (marketing, support, ops alerts).

Part 3: Sending Messages

Basic text message

bash

curl -X POST https://developers-api.chatmaid.net/v1/messages/send \ -H "Authorization: Bearer $CHATMAID_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "fromPhoneId": "+15551234567", "to": "+15557654321", "content": "Hello from the API!" }'

Response:

json

{ "messageId": "msg_abc123", "status": "queued", "createdAt": "2026-06-01T10:30:00Z" }

Idempotent sending

For any production system that retries requests, include an idempotency key. If you send the same request twice with the same key, only one message is delivered.

bash

curl -X POST https://developers-api.chatmaid.net/v1/messages/send \ -H "Authorization: Bearer $CHATMAID_API_KEY" \ -H "Idempotency-Key: order-shipped-order-4892-2026-06-01" \ -H "Content-Type: application/json" \ -d '{ "fromPhoneId": "+15551234567", "to": "+15557654321", "content": "Your order #4892 has shipped!" }'

Use a deterministic key that uniquely identifies the event: {event-type}-{entity-id}-{date}.

Sending media

json

{ "fromPhoneId": "+15551234567", "to": "+15557654321", "content": "Here is your invoice.", "media": { "url": "https://yourapp.com/invoices/inv-4892.pdf", "type": "document", "filename": "Invoice-4892.pdf" } }

Supported media types: image, document, audio, video.

Part 4: Client Libraries

Node.js

javascript

const chatmaid = require('@chatmaid/sdk'); // or use fetch directly const client = new chatmaid.Client({ apiKey: process.env.CHATMAID_API_KEY }); async function sendMessage(to, content) { const message = await client.messages.send({ fromPhoneId: process.env.SENDER_PHONE, to, content }); return message; }

If you prefer not to use the SDK, the native fetch API works directly:

javascript

async function sendWhatsApp(to, content) { const res = await fetch('https://developers-api.chatmaid.net/v1/messages/send', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.CHATMAID_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ fromPhoneId: process.env.SENDER_PHONE, to, content }) }); if (!res.ok) { const err = await res.json(); throw new Error(`Chatmaid error: ${err.error.code} — ${err.error.hint}`); } return res.json(); }

Python

python

import os import requests from typing import Optional CHATMAID_API_KEY = os.environ['CHATMAID_API_KEY'] SENDER_PHONE = os.environ['SENDER_PHONE'] BASE_URL = 'https://developers-api.chatmaid.net/v1' def send_whatsapp( to: str, content: str, idempotency_key: Optional[str] = None ) -> dict: headers = { 'Authorization': f'Bearer {CHATMAID_API_KEY}', 'Content-Type': 'application/json' } if idempotency_key: headers['Idempotency-Key'] = idempotency_key response = requests.post( f'{BASE_URL}/messages/send', headers=headers, json={ 'fromPhoneId': SENDER_PHONE, 'to': to, 'content': content } ) response.raise_for_status() return response.json()

Ruby

ruby

require 'net/http' require 'json' require 'uri' def send_whatsapp(to, content, idempotency_key: nil) uri = URI('https://developers-api.chatmaid.net/v1/messages/send') request = Net::HTTP::Post.new(uri) request['Authorization'] = "Bearer #{ENV['CHATMAID_API_KEY']}" request['Content-Type'] = 'application/json' request['Idempotency-Key'] = idempotency_key if idempotency_key request.body = { fromPhoneId: ENV['SENDER_PHONE'], to: to, content: content }.to_json Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end end

Part 5: Error Handling

Chatmaid returns structured errors with a type, code, and hint:

json

{ "error": { "type": "rate_limit", "code": "too_many_requests", "hint": "retry after 60s" } }

Error types you'll encounter:

Type

Code

Meaning

authentication

invalid_api_key

Bad or expired key

validation

invalid_phone

Phone number format error

not_found

phone_not_connected

The sender phone is disconnected

rate_limit

too_many_requests

Too many requests — back off

upstream

whatsapp_unavailable

WhatsApp delivery issue — retry

Implement a retry policy for rate_limit and upstream errors. Don't retry authentication or validation errors (they won't succeed without fixing the underlying issue).

python

import time def send_with_retry(to: str, content: str, max_retries: int = 3): for attempt in range(max_retries): try: return send_whatsapp(to, content) except requests.HTTPError as e: error = e.response.json().get('error', {}) if error.get('type') == 'rate_limit': wait = 60 * (2 ** attempt) # Exponential backoff time.sleep(wait) elif error.get('type') == 'upstream': time.sleep(5 * (2 ** attempt)) else: raise # Don't retry auth or validation errors raise Exception("Max retries exceeded")

Part 6: Webhooks (Receiving Messages)

Endpoint requirements

Your webhook endpoint must:

  • Be accessible over HTTPS
  • Return a 2xx status within 5 seconds
  • Accept POST requests with a JSON body

Registering your webhook

In the Chatmaid dashboard, go to Webhooks → Add. Enter your endpoint URL and select events. Copy the webhook secret.

Verifying signatures

javascript

function verifyWebhookSignature(body, signature, secret) { const crypto = require('crypto'); const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(JSON.stringify(body)) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); }

Always verify signatures. Without this check, anyone can POST fake events to your webhook URL.

Processing webhook events

javascript

app.post('/webhooks/chatmaid', (req, res) => { const sig = req.headers['x-chatmaid-signature']; if (!verifyWebhookSignature(req.body, sig, WEBHOOK_SECRET)) { return res.status(401).json({ error: 'Unauthorized' }); } // Respond immediately to prevent timeout res.status(200).json({ received: true }); // Process asynchronously setImmediate(() => { const { event, data } = req.body; switch (event) { case 'message.received': handleIncomingMessage(data); break; case 'message.delivered': handleDeliveryConfirmation(data); break; case 'message.failed': handleDeliveryFailure(data); break; } }); });

Part 7: Checking Message Status

Poll the status of a specific message:

bash

curl https://developers-api.chatmaid.net/v1/messages/msg_abc123 \ -H "Authorization: Bearer $CHATMAID_API_KEY"

json

{ "messageId": "msg_abc123", "status": "delivered", "sentAt": "2026-06-01T10:30:00Z", "deliveredAt": "2026-06-01T10:30:02Z" }

Status flow: queuedsentdeliveredread (or failed at any stage).

For production, prefer webhooks over polling. Polling works for one-off status checks; webhooks are better for real-time updates at scale.

Part 8: Rate Limits

Chatmaid's rate limit information is available via the X-RateLimit-* headers on every response:

X-RateLimit-Limit: 100 X-RateLimit-Remaining: 87 X-RateLimit-Reset: 1748772600

You can also query current usage programmatically. Build your sending queue to respect these limits and implement exponential backoff on 429 responses.

Part 9: Production Deployment Checklist

Before going live:

  • Swap sk_test_* for sk_live_* in production environment variables
  • Verify webhook signatures in your handler (don't skip this)
  • Add idempotency keys to all send calls
  • Implement retry logic with exponential backoff
  • Set up error alerting for message.failed webhook events
  • Store message IDs and phone numbers in your database for audit trails
  • Test the sandbox → production transition with a real phone number
  • Review your sending patterns: only message opt-in contacts

Part 10: The MCP Server for Development

If you're using Claude Code, Cursor, or Windsurf to build your integration, install the Chatmaid MCP server to give your agent direct WhatsApp access during development:

bash

claude mcp add chatmaid \ --env CHATMAID_API_KEY=sk_test_xxx \ -- npx -y @chatmaid/mcp

Your coding agent can then test your integration end-to-end — sending real sandbox messages and inspecting webhook payloads — without you having to manually curl the API.