The Complete WhatsApp API Developer Checklist: Build, Test, and Scale
The definitive WhatsApp API developer checklist for 2026. Cover setup, authentication, message sending, webhooks, error handling, rate limits, and production scaling with Rapiwa API.
This is the complete WhatsApp API developer checklist for 2026 — covering everything from initial account setup to production scaling. Use this as your reference guide when integrating Rapiwa's WhatsApp API ($5/month, no per-message fees) into any application. Check off each item before going live to avoid common issues.
What Is a WhatsApp API Developer Checklist?
A WhatsApp API developer checklist is a structured list of technical requirements, best practices, and tests to complete before deploying WhatsApp API integrations to production. It covers authentication, message sending, webhook handling, error management, rate limiting, and scaling. This checklist applies to Rapiwa and most unofficial WhatsApp REST APIs.
Phase 1: Account Setup and Configuration
Account Basics
- Created Rapiwa account at rapiwa.com (3-day free trial available)
- Verified email and completed account setup
- Selected the appropriate plan (Starter/Professional/Enterprise)
- Enabled 2FA on the Rapiwa account for security
WhatsApp Number Connection
- Connected WhatsApp number via QR code scan in Dashboard → Devices
- Verified the number shows "Connected" status in dashboard
- The connected number is active and has WhatsApp installed on a phone
- Tested receiving a message on the connected number before proceeding
- Documented which phone/SIM is connected (for reconnection if needed)
API Credentials
- Generated API key in Dashboard → API Keys
- Stored API key in environment variables (never hardcoded)
- Verified API key works with a test cURL:
curl -X POST https://app.rapiwa.com/send-message \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"number": "YOUR_TEST_PHONE", "message": "API key test!"}'
Expected: {"status": "success", "messageId": "..."}
- Configured API key in
.envor secrets manager:
RAPIWA_API_KEY=your_key_here
RAPIWA_BASE_URL=https://app.rapiwa.com
Phase 2: Sending Messages
Basic Text Messages
- Confirmed API endpoint:
POST https://app.rapiwa.com/send-message - Verified phone number format: international without
+(8801234567890, not+880-1234-567890) - Tested with minimum required fields:
{
"number": "8801234567890",
"message": "Hello from [Your App]!"
}
- Confirmed success response format:
{
"status": "success",
"messageId": "msg_abc123",
"timestamp": "2026-07-18T10:30:00Z"
}
Phone Number Handling
- Built phone number sanitization function:
def clean_phone(phone: str) -> str:
"""Remove all non-digit characters from phone number."""
return ''.join(c for c in phone if c.isdigit())
# Test cases
assert clean_phone('+880-1234-567890') == '8801234567890'
assert clean_phone('+44 7700 900123') == '447700900123'
assert clean_phone('+1 (212) 555-1234') == '12125551234'
- Tested with phone numbers from your primary target countries
- Added validation to reject obviously invalid numbers (less than 7 digits, more than 15 digits)
Message Formatting
- Tested WhatsApp text formatting:
*bold*renders correctly_italic_renders correctly~strikethrough~renders correctly\ncreates line breaks
- Ensured message length is under 4,096 characters
- Tested multi-line messages:
message = (
"Line 1\n"
"Line 2\n\n"
"*Bold section*\n"
"_Italic note_"
)
Media Messages
- Tested image sending via
/send-imageendpoint:- Image URL is publicly accessible HTTPS
- Image is under 5MB
- Supported format: JPEG, PNG, WebP
- Tested document sending via
/send-document:- Document URL is public HTTPS
- Document is under 100MB
- Filename is provided in the payload
Phase 3: Webhook Configuration
Webhook Setup
- Server has a publicly accessible HTTPS URL for webhook
- Webhook endpoint returns
200 OKwithin 10 seconds - Registered webhook URL in Rapiwa Dashboard → Webhooks
- Selected relevant events:
message.received - Tested webhook receipt with a manual trigger
Webhook Handler
- Implemented webhook handler that:
- Parses the JSON payload
- Responds with
200 OKimmediately - Processes business logic asynchronously (background task/queue)
@app.route('/webhook/whatsapp', methods=['POST'])
def handle_webhook():
payload = request.get_json()
# Return 200 immediately
response = jsonify({'status': 'ok'})
# Process in background
if payload.get('event') == 'message.received':
process_message_async(payload['data'])
return response, 200
- Implemented idempotency check (prevent double-processing the same
messageId):
def process_message_async(data: dict) -> None:
message_id = data.get('messageId')
if redis.exists(f'processed:{message_id}'):
return # Already processed
redis.setex(f'processed:{message_id}', 3600, '1') # Mark as processed for 1 hour
# ... process the message
- Handles all incoming message types:
text— parse and respond to text messagesimage— handle image attachmentsdocument— handle document attachments- Other types — graceful fallback
Phase 4: Error Handling
HTTP Error Codes
- Handles
401 Unauthorized(invalid API key):- Log the error
- Alert the engineering team (not silently fail)
- Handles
400 Bad Request(invalid payload):- Log the full request and response
- Validate input before sending to avoid 400s
- Handles
429 Too Many Requests(rate limit):- Implement exponential backoff: wait 1s, 2s, 4s, 8s...
- Queue messages during rate limit windows
- Handles
5xx Server Errors(Rapiwa API issues):- Retry up to 3 times with backoff
- Alert on repeated 5xx errors
Error Handling Implementation
import time
import requests
def send_with_retry(phone: str, message: str, api_key: str, max_retries: int = 3) -> dict:
"""Send WhatsApp message with automatic retry on failure."""
last_error = None
for attempt in range(max_retries):
try:
response = requests.post(
'https://app.rapiwa.com/send-message',
headers={'Authorization': f'Bearer {api_key}'},
json={'number': phone, 'message': message},
timeout=15
)
if response.status_code == 401:
raise PermissionError("Invalid API key — check RAPIWA_API_KEY env var")
if response.status_code == 429:
wait_time = 2 ** attempt # Exponential backoff
time.sleep(wait_time)
continue
if response.status_code >= 500:
wait_time = 2 ** attempt
time.sleep(wait_time)
continue
return response.json()
except requests.Timeout:
last_error = "Request timed out"
time.sleep(2 ** attempt)
continue
except requests.ConnectionError:
last_error = "Connection error — check network"
time.sleep(5)
continue
raise RuntimeError(f"Failed after {max_retries} attempts: {last_error}")
Phase 5: Rate Limiting and Bulk Sending
Rate Limit Compliance
- Added 1-second delay between messages for bulk sends
- Implemented a message queue for high-volume sends (Celery, Bull, Sidekiq):
# Celery task for rate-limited bulk send
@celery.task(rate_limit='60/m') # 60 per minute max
def send_whatsapp_task(phone: str, message: str) -> dict:
return send_with_retry(phone, message, os.environ['RAPIWA_API_KEY'])
- Implemented a daily volume monitor (alert if unexpected spike):
def check_daily_volume():
"""Alert if daily message count exceeds expected threshold."""
count = redis.incr('daily_message_count')
if count > EXPECTED_DAILY_MAX * 1.5:
send_alert(f"⚠️ Unusual message volume: {count} messages sent today")
Phase 6: Security
Security Checklist
- API key stored in environment variable (not in source code)
- API key NOT committed to git:
# .gitignore
.env
config/secrets.yml
*.key
- API key NOT logged in application logs
- Phone numbers NOT logged in plaintext (mask in logs:
880***1234) - Webhook endpoint validates that requests come from expected source
- Input validation on all phone numbers before calling API
- Implemented rate limiting on your own webhook endpoint to prevent abuse
Phase 7: Testing
Pre-Production Test Suite
- Unit tests for phone number sanitization
- Unit tests for message template building
- Integration test: send a real message to your own test number
- Integration test: receive a webhook from a test message
- Load test: send 100 messages in sequence (verify no errors)
- Error test: send with invalid API key → verify 401 is handled
- Error test: send to invalid phone number → verify 400 is handled
# test_rapiwa.py
import pytest
from unittest.mock import patch, Mock
def test_clean_phone():
assert clean_phone('+880-1234-567890') == '8801234567890'
assert clean_phone('+44 7700 900123') == '447700900123'
def test_send_message_success():
with patch('requests.post') as mock_post:
mock_post.return_value = Mock(
status_code=200,
json=lambda: {'status': 'success', 'messageId': 'msg_test_123'}
)
result = send_with_retry('8801234567890', 'Test', 'fake_key')
assert result['status'] == 'success'
def test_send_message_401():
with patch('requests.post') as mock_post:
mock_post.return_value = Mock(status_code=401, json=lambda: {})
with pytest.raises(PermissionError):
send_with_retry('8801234567890', 'Test', 'invalid_key', max_retries=1)
Phase 8: Monitoring and Production
Production Monitoring
- Set up monitoring for API response times (alert if >5 seconds)
- Set up monitoring for error rates (alert if >5% failure rate)
- Set up alerting for API key expiry
- Set up dashboard for messages sent/received per day
- Created runbook for common issues:
ISSUE: Messages not sending (401)
ACTION: Log in to Rapiwa dashboard → API Keys → regenerate key → update env var
ISSUE: WhatsApp number disconnected
ACTION: Log in to Rapiwa dashboard → Devices → scan QR code again on the phone
ISSUE: Webhook not receiving
ACTION: Check n8n/server is running → verify URL in Rapiwa webhook settings → send test message
Go-Live Checklist
- All tests passing
- Error handling tested for all HTTP status codes
- Rate limiting implemented
- Phone number validation in place
- Monitoring and alerts configured
- Runbook written and team trained
- API key secured in production secrets manager
- Webhook endpoint behind HTTPS
- Sent 10 test messages across different message types
- Received and processed at least 5 test webhook events
Quick Reference: Rapiwa API at a Glance
| Item | Value |
|---|---|
| Base URL | https://app.rapiwa.com |
| Send text | POST /send-message |
| Send image | POST /send-image |
| Send document | POST /send-document |
| Auth header | Authorization: Bearer YOUR_API_KEY |
| Phone format | International without + (e.g., 8801234567890) |
| Max message length | 4,096 characters |
| Success status | {"status": "success", "messageId": "..."} |
| Pricing | $5/month flat, no per-message fees |
| Free trial | 3 days, no credit card |
| Sourceforge rating | 5.0/5 |
FAQ
What is the most common mistake developers make with the Rapiwa API?
Phone number format. Always use international format without + or spaces: 8801234567890, not +880 1234 567890. Strip all non-digit characters before sending.
How do I handle the WhatsApp number disconnecting in production? Build an uptime monitor that sends a test message every 6 hours. If it fails with anything other than a business error, alert the team to re-scan the QR code in the Rapiwa dashboard.
Does Rapiwa have an API version I need to specify? Rapiwa's API doesn't require explicit versioning in the URL. The current API version is stable — changes are backwards-compatible.
What is the best way to store the Rapiwa API key in production?
Use your cloud provider's secrets manager: AWS Secrets Manager, Google Cloud Secret Manager, HashiCorp Vault, or at minimum environment variables in a .env file not committed to git.
Can I use the same Rapiwa API key across multiple servers? Yes. The API key is not tied to a specific IP address. You can use it from multiple servers simultaneously — useful for high-availability setups with multiple application instances.
