Requirements
- Target platform
- OpenClaw
- Install method
- Manual import
- Extraction
- Extract archive
- Prerequisites
- OpenClaw
- Primary doc
- SKILL.md
Use when implementing WhatsApp messaging via Meta Cloud API, or diagnosing failures like message not delivered, template rejected, webhook issues, phone not...
Use when implementing WhatsApp messaging via Meta Cloud API, or diagnosing failures like message not delivered, template rejected, webhook issues, phone not...
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete.
I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run.
The Meta WhatsApp Cloud API is the official, fully hosted path for programmatic WhatsApp messaging. No server management needed. First 1,000 service conversations per month are free. Key rules: Your app can NEVER send a free-form text message first. The very first message to any user must always be a pre-approved template. Free-form text is only unlocked after the user replies, and only within the 24-hour window that reply opens. Business-initiated messages outside a 24h reply window must use a pre-approved template Phone numbers must be registered in your WABA before sending Always use a System User token โ user tokens expire in 24 hours Conversation flow: App โ user: MUST be a template (always, for first contact) User โ app: reply opens a 24-hour free-form window App โ user: free-form text allowed within that 24h window [24h passes with no user reply] App โ user: MUST use a template again to re-engage
Create Meta Developer App โ developers.facebook.com โ Create App โ Business type Add WhatsApp product to the app (gives temp test number + 5 test recipient slots) Create a permanent System User token: Meta Business Manager โ Settings โ System Users โ Create Admin user Assign permissions: whatsapp_business_messaging + whatsapp_business_management Generate token โ this never expires Register real phone number โ number cannot already be active on personal/business WhatsApp Set up webhook โ needs public HTTPS URL (trusted CA cert, no self-signed), must respond in < 10s
// npm install axios const axios = require('axios'); async function sendMessage(phoneNumber, text) { // phoneNumber: E.164 without +, e.g. "14155551234" const res = await axios.post( `https://graph.facebook.com/v21.0/${process.env.WA_PHONE_NUMBER_ID}/messages`, { messaging_product: 'whatsapp', recipient_type: 'individual', to: phoneNumber, type: 'text', text: { preview_url: false, body: text } }, { headers: { Authorization: `Bearer ${process.env.WA_ACCESS_TOKEN}` } } ); return res.data; }
# pip install requests import requests, os def send_message(phone: str, text: str) -> dict: r = requests.post( f"https://graph.facebook.com/v21.0/{os.environ['WA_PHONE_NUMBER_ID']}/messages", headers={"Authorization": f"Bearer {os.environ['WA_ACCESS_TOKEN']}"}, json={ "messaging_product": "whatsapp", "recipient_type": "individual", "to": phone, # E.164 without + "type": "text", "text": {"preview_url": False, "body": text} } ) r.raise_for_status() return r.json()
curl -X POST "https://graph.facebook.com/v21.0/YOUR_PHONE_ID/messages" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"messaging_product":"whatsapp","to":"14155551234","type":"text","text":{"body":"Hello"}}'
const payload = { messaging_product: 'whatsapp', to: phoneNumber, type: 'template', template: { name: 'hello_world', // your approved template name language: { code: 'en_US' }, components: [{ type: 'body', parameters: [ { type: 'text', text: 'John' }, // fills {{1}} { type: 'text', text: 'Order #4521' } // fills {{2}} ] }] } };
Image: const payload = { messaging_product: 'whatsapp', to: phoneNumber, type: 'image', image: { link: 'https://your-domain.com/image.jpg' // must be publicly accessible HTTPS } }; Document: const payload = { messaging_product: 'whatsapp', to: phoneNumber, type: 'document', document: { link: 'https://your-domain.com/file.pdf', caption: 'Invoice' // optional } }; Audio: const payload = { messaging_product: 'whatsapp', to: phoneNumber, type: 'audio', audio: { link: 'https://your-domain.com/audio.mp3' } }; Video: const payload = { messaging_product: 'whatsapp', to: phoneNumber, type: 'video', video: { link: 'https://your-domain.com/video.mp4', caption: 'Demo video' // optional } }; Important constraints: All media URLs must be publicly accessible HTTPS (http:// fails) Max file sizes: Image 16MB, Document 100MB, Audio 16MB, Video 16MB Supported formats: Images (JPEG, PNG), Documents (PDF), Audio (AAC, MP3, OGG, WAV), Video (MP4, 3GPP) Media must not require authentication URLs cannot use shorteners (bit.ly, tinyurl, etc.)
// GET โ Meta calls this to verify your endpoint app.get('/webhook', (req, res) => { const { 'hub.mode': mode, 'hub.verify_token': token, 'hub.challenge': challenge } = req.query; if (mode === 'subscribe' && token === process.env.VERIFY_TOKEN) return res.status(200).send(challenge); // raw string only โ NOT JSON res.sendStatus(403); }); // POST โ CRITICAL: return 200 IMMEDIATELY, process async app.post('/webhook', express.json(), (req, res) => { // Return 200 immediately so Meta doesn't retry res.sendStatus(200); // Process webhook payload asynchronously (don't block) setImmediate(() => { processWebhookAsync(req.body).catch(err => { logger.error(`Webhook processing failed: ${err.message}`); }); }); }); async function processWebhookAsync(body) { body.entry?.forEach(entry => entry.changes?.forEach(change => { const value = change.value; // Incoming messages if (value.messages) { value.messages.forEach(msg => { console.log(`Message from ${msg.from}: ${msg.text?.body}`); handleMessage(msg); }); } // Delivery status if (value.statuses) { value.statuses.forEach(status => { console.log(`Message ${status.id} status: ${status.status}`); handleDeliveryStatus(status); }); } }) ); }
Meta sends status updates via webhook when a message is delivered, read, or fails: { "object": "whatsapp_business_account", "entry": [{ "changes": [{ "value": { "statuses": [{ "id": "wamid.xxx", // Message ID from your send response "status": "delivered", // "sent" | "delivered" | "read" | "failed" "timestamp": "1675262308", "recipient_id": "14155551234", "type": "message" }] } }] }] } Status values: sent โ Message reached Meta servers delivered โ Message delivered to user's device read โ User opened the message failed โ Delivery failed (permanent)
// When you send, store the message ID const sendResult = await sendMessage(phone, text); const messageId = sendResult.messages[0].id; // Log it for webhook tracking db.messages.insert({ message_id: messageId, recipient: phone, sent_at: Date.now(), status: 'sent', body: text }); // When webhook arrives with status update, match by message_id function handleDeliveryStatus(statusUpdate) { const { id, status, recipient_id } = statusUpdate; // Update your database db.messages.updateOne( { message_id: id }, { status: status, updated_at: Date.now() } ); // Handle delivery failures if (status === 'failed') { logger.error(`Message ${id} failed to deliver to ${recipient_id}`); // Retry logic here } }
Your WhatsApp Business Account (WABA) has a quality rating that affects your sending ability: RatingImpactRecoveryGREENFull functionality, no restrictionsMaintain this (stay green)YELLOWSlight rate limit reduction, monitor closelyImprove within 7 days or drops to REDREDSevere restrictions, may lose messaging accessContact Meta Support
curl "https://graph.facebook.com/v21.0/PHONE_NUMBER_ID?fields=quality_rating" \ -H "Authorization: Bearer YOUR_TOKEN" # Response: { "quality_rating": "GREEN" }
High bounce rate โ sending to invalid/inactive numbers Spam reports โ users marking your messages as spam High failure rate โ messages consistently failing to deliver User blocks โ users blocking your number after messages Policy violations โ sending prohibited content
Validate phone numbers before sending โ use the WhatsApp contacts check (error 131026) Only message opted-in users โ don't send unsolicited messages Keep template content transactional โ avoid marketing spam Monitor quality metrics โ check rating regularly via API Respect user preferences โ remove users who opt out Don't retry failed numbers aggressively โ wait before retrying same number
Note: WhatsApp Business API does NOT support group messaging directly. You can only send to individual recipients (1:1 conversations). If you need group functionality: Users must add your business number to a group manually Messages sent to the group are treated as individual 1:1 messages You cannot initiate group conversations programmatically
All examples use v21.0 (current as of February 2026). Meta deprecates API versions annually.
# List all available versions curl "https://graph.facebook.com/versions" \ -H "Authorization: Bearer YOUR_TOKEN" # Current version recommendations # - v21.0 (current, recommended) # - v20.0 (previous, will deprecate in 6 months)
// Store version in config, not hardcoded const API_VERSION = process.env.WHATSAPP_API_VERSION || 'v21.0'; const url = `https://graph.facebook.com/${API_VERSION}/${PHONE_NUMBER_ID}/messages`; // When Meta deprecates a version, update .env: // WHATSAPP_API_VERSION=v22.0
New message types or features added Deprecated fields removed Error codes may change slightly Response payload structure may change Always test before upgrading โ make requests against the new version in your dev environment first.
ConstraintLimitText body max length4,096 charactersLink previewEnabled by default, disable with preview_url: falseCarriage returns / newlinesSupported (use \n)
TypeItemsCharacter LimitButton1-3Title: 20 charsList1-10Title: 24 chars per row
Must be HTTPS only (http:// rejected) Must be publicly accessible (no authentication required) Must NOT use shorteners (bit.ly, tinyurl rejected) Must have correct Content-Type header Max file sizes: Image: 16 MB Audio: 16 MB Video: 16 MB Document: 100 MB
LimitValueDefault throughput80 messages/secondBurst capacity1,000 messages/second (request increase)Requests per minute60 API calls/minute
Create template in Meta Business Manager โ Submit for review (human review by Meta) โ Status: PENDING (24-72 hours typical) โ Status: APPROVED (can now use in messages) OR Status: REJECTED (reason provided in dashboard)
Typical: 24-72 hours Peak times (weekends, holidays): up to 7 days Fast-track: Available for high-volume WABAs (request in Meta Support)
IssueWhy RejectedVariable formatMust use {{1}}, {{2}} formatTemplate starts/ends with variableMust have text before first variableURL shortenersUse full domain URLs onlyPlaceholder qualityPlaceholder values must be realistic examplesSensitive data requestNever ask for SSN, card numbers, passwordsUnclear purposePurpose field must clearly state intentWarm language in utilityUse formal wording; warmth triggers "marketing" category (costs more)Duplicate templateName/wording too similar to existing template Check rejection reason in Meta Business Manager โ Business Support โ Rejected Template Messages.
Symptom: Registration fails with error 133010, status stays PENDING Cause: Phone number is already active on a personal WhatsApp account Fix: 1. Remove phone from personal WhatsApp (go to Settings โ Devices โ Remove phone) 2. Wait 24 hours 3. Re-run registration API call
Symptom: API returns "Invalid parameters" for phone operations How to tell them apart: PHONE_NUMBER_ID: 120######## (11-12 digits, starts with 120) WABA_ID: ######### (9-10 digits, higher number) # Correct endpoint POST /v21.0/PHONE_NUMBER_ID/messages โ # Wrong endpoint POST /v21.0/WABA_ID/messages โ
Symptom: Token debug shows valid, but messaging fails with error 3/10 # Token is "valid" but missing scopes curl "https://graph.facebook.com/debug_token?input_token=TOKEN&access_token=TOKEN" # Response: { "is_valid": true, "scopes": ["manage_pages"] } โ NO whatsapp_business_messaging # Fix: Regenerate System User token with correct permissions
Symptom: Webhook verification fails silently in Meta dashboard Wrong: return res.json({ challenge }); // โ returns JSON Correct: return res.status(200).send(challenge); // โ returns raw string
Symptom: Webhooks never arrive (silent failure since 2025 Meta UI change) Fix: curl -X POST \ "https://graph.facebook.com/v21.0/WABA_ID/subscribed_apps" \ -H "Authorization: Bearer YOUR_SYSTEM_USER_TOKEN"
Symptom: Error 132001 "Template Unavailable" Cause: Template still in PENDING status, not yet APPROVED Fix: Check status in Meta Business Manager โ Message Templates โ wait for APPROVED status
Symptom: Error 131009 when trying to send How to verify: # Check which phone numbers are in your WABA curl "https://graph.facebook.com/v21.0/WABA_ID/phone_numbers?fields=id,display_phone_number" \ -H "Authorization: Bearer YOUR_TOKEN"
Symptom: API returns 200, message ID issued, but user never receives it Root cause: Only templates allowed as first message to any number Fix: Always use a template for first contact
Check the status field via API. Each status blocks different operations: StatusMeaningActionPENDINGNumber is registered but not verifiedSet up 2FA (either manual or API), then run register callREGISTEREDNumber is verified and readyCheck code_verification_status โ should be VERIFIEDFLAGGEDAccount or number under review for policy violationContact Meta SupportBANNEDNumber permanently disabledContact Meta Support
CodeNameCauseFix190Token ExpiredUser token (24h lifetime) used in productionSwitch to System User token; debug at developers.facebook.com/tools/debug/accesstoken3 / 10Permission DeniedToken missing required scopesRegenerate System User token with whatsapp_business_messaging + whatsapp_business_management100Invalid ParameterMisspelled field or wrong valueCheck request body against API docs; verify phone number format (E.164, no +)130429Rate Limit (MPS)Exceeded 80 messages/sec defaultAdd send queue + exponential backoff (see below)13104724h Window Expired> 24h since customer last repliedReplace free-form text with a pre-approved template message131026UndeliverableRecipient blocked you, no WhatsApp, or outdated appVerify recipient number; confirm they have WhatsApp installed and accepted Meta terms131048Spam Rate LimitMessages flagged as spamCheck Quality Rating in WhatsApp Manager; review message content and opt-in practices131056Pair Rate LimitToo many messages to same recipient too fastWait before retrying the same number131009Invalid Parameter ValuePhone number not in WABA, or wrong parameterVerify number is registered in your WABA under Phone Numbers131021Same Sender/Recipientfrom and to are the same numberUse a different recipient131031Account LockedPolicy violation or wrong 2-step PINContact Meta Support132001Template UnavailableWrong template name, wrong language code, or not yet approvedCheck WhatsApp Manager โ Message Templates for exact name, language, and status133010Phone Not RegisteredSender number not registered in Cloud APIRun the registration API call (see below)368Policy ViolationAccount restrictedContact Meta Support1 / 2API Service ErrorMeta outage or server errorCheck metastatus.com; retry with exponential backoff
Diagnose first: curl "https://graph.facebook.com/debug_token?input_token=YOUR_TOKEN&access_token=YOUR_APP_ID|YOUR_APP_SECRET" Check is_valid, expires_at (0 = never expires), and scopes in the response. Fix โ create a non-expiring System User token: Meta Business Manager โ Settings โ System Users Create Admin system user Add whatsapp_business_messaging + whatsapp_business_management permissions Generate token โ never set an expiry
Step 1 โ check phone number status: curl "https://graph.facebook.com/v21.0/PHONE_NUMBER_ID?fields=verified_name,code_verification_status,quality_rating,status" \ -H "Authorization: Bearer YOUR_TOKEN" If status is PENDING, the number is waiting for verification. Continue below. Step 2 โ set up Two-Step Verification (choose ONE method): Option A: Manual setup (easy) Meta Business Manager โ WhatsApp Settings โ Phone Numbers โ Your Number Click "Two-Step Verification" โ set a PIN Option B: API setup (easier for automation) # Set 2FA PIN via API curl -X POST \ "https://graph.facebook.com/v21.0/PHONE_NUMBER_ID/two_step_verification" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"pin": "123456"}' # any 6-digit PIN you choose Step 3 โ register the number with the PIN: curl -X POST \ "https://graph.facebook.com/v21.0/PHONE_NUMBER_ID/register" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"messaging_product": "whatsapp", "pin": "123456"}' # same PIN from step 2 Step 4 โ wait 5 minutes, then verify registration: curl "https://graph.facebook.com/v21.0/PHONE_NUMBER_ID?fields=verified_name,code_verification_status,quality_rating,status" \ -H "Authorization: Bearer YOUR_TOKEN" Look for status: REGISTERED and code_verification_status: VERIFIED.
Diagnose in this order: Not returning raw challenge โ endpoint must return the hub.challenge string value only, not JSON Token mismatch โ hub.verify_token Meta sends must match exactly what you set in the dashboard (case-sensitive) SSL issue โ Meta requires a valid cert from a trusted CA; self-signed certs are rejected Timeout โ your server must respond within 10 seconds WABA not subscribed to App โ common silent failure since 2025 Meta UI change: # Subscribe your WABA to your App (run once) curl -X POST \ "https://graph.facebook.com/v21.0/WABA_ID/subscribed_apps" \ -H "Authorization: Bearer YOUR_SYSTEM_USER_TOKEN" # Verify the subscription exists curl "https://graph.facebook.com/v21.0/WABA_ID/subscribed_apps" \ -H "Authorization: Bearer YOUR_SYSTEM_USER_TOKEN" Local development โ expose localhost with a tunnel: # Using ngrok ngrok http 3000 # Use the https:// URL ngrok provides as your webhook callback URL in Meta
Default limit is 80 messages per second. Fix with a queue and exponential backoff: // npm install limiter const { RateLimiter } = require('limiter'); const limiter = new RateLimiter({ tokensPerInterval: 70, interval: 'second' }); async function sendWithRetry(phone, message, attempt = 0) { await limiter.removeTokens(1); try { return await sendMessage(phone, message); } catch (err) { const code = err.response?.data?.error?.code; if (code === 130429 && attempt < 5) { const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s, 8s, 16s await new Promise(r => setTimeout(r, delay)); return sendWithRetry(phone, message, attempt + 1); } throw err; } } To increase throughput beyond 80 MPS, apply in Meta Business Manager โ WhatsApp โ Phone Numbers โ Request Increased Messaging Limit.
You cannot send free-form text to a user more than 24 hours after their last message. You must use a template. // Instead of free-form text, send an approved template await sendTemplate(phoneNumber, 'order_update', 'en_US', [ { type: 'text', text: 'John' }, { type: 'text', text: '#4521' } ]); Create and submit templates at: Meta Business Manager โ WhatsApp โ Message Templates.
Rejection ReasonFixVariable format wrongUse {{1}}, {{2}} โ double curly braces, sequential integers onlyTemplate starts/ends with variableAdd plain text before {{1}} and after the last variableVariables not sequentialMust be {{1}}, {{2}} โ no gaps allowedURL shorteners usedUse full, unshortened URLs to your own domainLanguage code mismatchMatch language.code to the actual content language, e.g. en_US, pt_BRWarm language in utility templateUse formal transactional wording; warm language causes auto-reclassification to marketing categorySensitive dataNever request SSNs, full card numbers, or passwordsDuplicate of existing templateChange the wording โ even minor variation is requiredPurpose unclearEach variable must have a descriptive example value in the template submission Where to find rejection reason: Meta Business Manager โ Business Support Home โ Your WhatsApp Account โ Rejected Template Messages โ view policy issue.
Run this to check if a number has WhatsApp before sending: curl "https://graph.facebook.com/v21.0/PHONE_NUMBER_ID/contacts" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -X POST \ -d '{"messaging_product": "whatsapp", "contacts": ["+14155551234"]}' # Response includes "wa_id" if the number has WhatsApp, empty if not
This is the most common silent failure. The API returns success (messages[0].id) but the user receives nothing. Root cause: You sent a free-form text message as the first outreach. Meta silently drops it. How to tell: Check the message status webhook โ the message will show failed with error 131047 or show sent but never delivered. Rule: The very first message your app sends to any number must be a template. No exceptions. โ Wrong โ app sends free-form text first: POST /messages โ type: "text", body: "Hello John, your order is ready" โ API may return 200 but message is silently dropped or returns 131047 โ Correct โ app sends template first: POST /messages โ type: "template", name: "order_ready" โ User receives the message and can reply โ After user replies, free-form text is allowed for 24h Fix: Create and approve a template for every type of first-contact message you need to send. Submit templates at Meta Business Manager โ WhatsApp โ Message Templates.
Always validate phone number format and WhatsApp registration before sending: def is_valid_phone_format(phone_digits: str) -> bool: """ E.164 format validation: - 7-15 digits (not including +) - Not all same digit (e.g., 0000000 is invalid) """ if not phone_digits or len(phone_digits) < 7 or len(phone_digits) > 15: return False if len(set(phone_digits)) == 1: # all same digit return False return True def is_registered_on_whatsapp(phone_digits: str, token: str, phone_id: str) -> bool: """ Check if number is a registered WhatsApp user. Returns False only on definitive error 131026 (not on WhatsApp). Returns True if successful, uncertain (auth error, etc.), or timeout. """ headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" } data = { "messaging_product": "whatsapp", "to": phone_digits, "type": "text", "text": {"body": "_"} # minimal text to check } try: r = requests.post( f"https://graph.facebook.com/v21.0/{phone_id}/messages", headers=headers, json=data, timeout=10 ) if r.status_code == 200: return True # number is valid error_code = r.json().get("error", {}).get("code") if error_code == 131026: return False # NOT on WhatsApp return True # other errors โ don't block except: return True # network error โ don't block
def extract_error_code(response_json: dict) -> int: """Extract error code from Meta API response.""" return ( response_json.get("error", {}).get("code") or response_json.get("error", {}).get("error_subcode") ) def send_with_error_handling(phone_id: str, recipient: str, message_body: str, token: str): """Send message and extract detailed error info.""" url = f"https://graph.facebook.com/v21.0/{phone_id}/messages" headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" } data = { "messaging_product": "whatsapp", "to": recipient, "type": "text", "text": {"body": message_body} } try: r = requests.post(url, headers=headers, json=data) r.raise_for_status() return {"success": True, "message_id": r.json().get("messages")[0].get("id")} except requests.HTTPError as e: error_code = extract_error_code(e.response.json()) error_msg = e.response.json().get("error", {}).get("message") return { "success": False, "error_code": error_code, "error_message": error_msg, "response_text": e.response.text }
def send_interactive_message(phone_id: str, recipient: str, text: str, options: list, token: str): """ Intelligently sends: - Buttons (up to 3 options) - List Menu (4-10 options) Each option: {"id": "unique_id", "title": "Text (max 20 chars)"} """ url = f"https://graph.facebook.com/v21.0/{phone_id}/messages" headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" } if len(options) <= 3: # Send as buttons button_data = { "messaging_product": "whatsapp", "to": recipient, "type": "interactive", "interactive": { "type": "button", "body": {"text": text}, "action": { "buttons": [ { "type": "reply", "reply": {"id": opt["id"], "title": opt["title"][:20]} } for opt in options ] } } } return requests.post(url, headers=headers, json=button_data) else: # Send as list menu list_data = { "messaging_product": "whatsapp", "to": recipient, "type": "interactive", "interactive": { "type": "list", "body": {"text": text}, "action": { "button": "See options", "sections": [{ "title": "Available options", "rows": [ { "id": opt["id"], "title": opt["title"][:24] } for opt in options ] }] } } } return requests.post(url, headers=headers, json=list_data)
Token & Phone Verification Script: # Check if token is valid and phone number is accessible import requests, os from dotenv import load_dotenv load_dotenv() TOKEN = os.getenv('WHATSAPP_TOKEN') PHONE_ID = os.getenv('PHONE_NUMBER_ID') # Debug token r = requests.get(f"https://graph.facebook.com/debug_token?input_token={TOKEN}&access_token={TOKEN}") data = r.json() if 'error' in data: print(f"โ Token Error: {data['error']['message']}") else: print(f"โ Token Valid") print(f" Expires: {data['data'].get('expires_at')} (0=never)") print(f" Scopes: {data['data'].get('scopes')}") # Check phone number r = requests.get( f"https://graph.facebook.com/v21.0/{PHONE_ID}", headers={"Authorization": f"Bearer {TOKEN}"} ) if r.status_code == 200: print(f"โ Phone Number Accessible") print(f" Display: {r.json().get('display_phone_number')}") print(f" Quality Rating: {r.json().get('quality_rating')}") else: print(f"โ Phone Error: {r.json().get('error', {}).get('message')}")
When a message fails, check in this order: Token valid? โ curl "https://graph.facebook.com/debug_token?input_token=TOKEN&access_token=APP_ID|APP_SECRET" Phone number format? โ E.164, no +, no spaces: "14155551234" Number registered in WABA? โ check WhatsApp Manager โ Phone Numbers Number registered with Cloud API? โ run registration call if error 133010 24h window? โ if > 24h since last user reply, send a template instead Template approved? โ WhatsApp Manager โ Message Templates โ check status and rejection reason Webhook subscribed? โ verify WABA โ App subscription via GET /WABA_ID/subscribed_apps Rate limited? โ check Quality Rating in WhatsApp Manager; implement backoff + queue
Code helpers, APIs, CLIs, browser automation, testing, and developer operations.
Largest current source with strong distribution and engagement signals.