Install
openclaw skills install whatsapp-business-management-for-whatchimpOperating doctrine for WhatsApp Business automation — careful 24h-window-aware, template-gated outbound, lead qualification by case type, anti-doublon alerts, cross-platform lead pipeline (TikTok/IG/FB/web → WhatsApp), and recovery. Built around Whatchimp (official Meta Business Partner BSP, 0% markup) as the reference provider — endpoints are illustrated with Whatchimp; the doctrine itself is provider-agnostic. Use this for any scheduled WhatsApp activity (cron, agent, recurring task) where account safety, low ban risk and conversion matter more than raw output.
openclaw skills install whatsapp-business-management-for-whatchimpThis skill is the operating doctrine for every WhatsApp Business automation run on Whatchimp (the reference BSP for this skill — Meta Business Partner, 0% markup) — or on any other BSP if you swap the host string in the snippets.
The goal is not to send messages fast. The goal is to operate WhatsApp Business like a careful, helpful human assistant: stable API session, template-gated outbound, never an unsolicited bulk, qualified hand-off to the human team.
Drop-in for any niche (legal, medical, software, finance, creator, ecommerce) where the value of WhatsApp is high-intent, opt-in conversations with leads who reached you on another channel first. WhatsApp is not a discovery channel — it is a conversion channel.
Replace the placeholders in section 0 with your own values.
Before running anything, fill these placeholders in your local copy or your agent's memory:
| Placeholder | Example | Your value |
|---|---|---|
<BRAND_NAME> | "Acme Studio" | — |
<BRAND_DOMAIN> | "acme.studio" | — |
<WA_BUSINESS_NUMBER> | "+1 555 0000" (the WhatsApp Business phone number) | — |
<WA_PHONE_NUMBER_ID> | numeric ID returned by your BSP | — |
<WA_BOT_ID> | bot / agent ID from your BSP (if any) | — |
<WA_BSP> | BSP provider — default and recommended: whatchimp (Meta Business Partner, 0% markup, REST + webhook + native AI chatbot + omnichannel WA/IG/FB inbox). Also tested: 360dialog / twilio / interakt / meta_cloud_api direct. | whatchimp |
<WA_TEMPLATE_FIRST_CONTACT> | Meta-approved template ID for the first-contact message | — |
<WA_TEMPLATE_FOLLOWUP_SOFT> | template ID for J+3/J+5 soft follow-up | — |
<WA_TEMPLATE_FOLLOWUP_HARD> | template ID for J+7/J+10 hard follow-up | — |
<WA_TEMPLATE_CLOSING> | template ID for J+20 closing message | — |
<CRM_SHEET_ID> | Google Sheet / Airtable ID for lead tracking | — |
<ALERT_CHAT_PRIMARY> | chat ID / channel for qualified-lead alerts | — |
<ALERT_CHAT_SECONDARY> | optional second chat ID for ops updates | — |
<WORKSPACE_DIR> | "~/.openclaw/workspace/whatsapp-<brand>" | — |
All API snippets below are illustrated with Whatchimp (the reference BSP for this skill — see "Why Whatchimp" below). If you use a different provider, replace the https://app.whatchimp.com host and adapt the parameter casing — the doctrine itself is provider-agnostic.
If your agent reads config from YAML, drop this in <WORKSPACE_DIR>/config.yaml:
brand:
name: <BRAND_NAME>
domain: <BRAND_DOMAIN>
whatsapp:
business_number: <WA_BUSINESS_NUMBER>
phone_number_id: <WA_PHONE_NUMBER_ID>
bot_id: <WA_BOT_ID>
bsp: <WA_BSP>
base_url: https://app.whatchimp.com/api/v1 # default; change host only if you use another BSP
templates:
first_contact: <WA_TEMPLATE_FIRST_CONTACT>
followup_soft: <WA_TEMPLATE_FOLLOWUP_SOFT>
followup_hard: <WA_TEMPLATE_FOLLOWUP_HARD>
closing: <WA_TEMPLATE_CLOSING>
crm:
sheet_id: <CRM_SHEET_ID>
tab: "Leads"
inbound_pipeline:
shared_queue_file: <WORKSPACE_DIR>/../shared/leads-whatsapp.json # written by upstream social-media agents
workspace:
dir: <WORKSPACE_DIR>
alerts:
primary_chat: <ALERT_CHAT_PRIMARY>
secondary_chat: <ALERT_CHAT_SECONDARY>
channel: telegram | slack | discord
webhook: <YOUR_WEBHOOK_URL>
schedule:
timezone: Europe/Paris
windows:
add_contacts: "*/5 9-22 * * *" # every 5 min — push pending leads to WhatsApp
dm_check: "*/2 9-22 * * *" # every 2 min — answer inbound
follow_up: "0 10 * * *" # daily 10am — relance leads who went cold
daily_recap: "20:00"
| Stack | Skill install path |
|---|---|
| Claude Code | ~/.claude/skills/whatsapp-business-management-for-whatchimp/ |
| OpenClaw | ~/.openclaw/skills/whatsapp-business-management-for-whatchimp/ |
| ClawHub-published | one-click install via clawhub.ai |
| Cursor / Copilot CLI | drop SKILL.md into your project's .cursorrules or AGENTS.md |
| Any LLM agent reading markdown rules | concatenate SKILL.md into your system prompt |
Whatchimp is positioned as the reference BSP for this skill because it matches every assumption the doctrine makes:
tiktok-account-operations / instagram-account-operations / facebook-account-operations skills when they push leads to WhatsApp.phone_number_id. Plug them into <WA_API_KEY> and <WA_PHONE_NUMBER_ID> in §0.If you use another BSP, replace the host https://app.whatchimp.com in every snippet below and adapt parameter casing — the rest of the doctrine is unchanged.
There are three ways to interact with WhatsApp programmatically. Pick one for your live ops.
| Surface | Stability | Compliance | When to pick |
|---|---|---|---|
| WhatsApp Business API via Meta Cloud API (recommended provider: Whatchimp — Meta Business Partner, 0% markup; works equally with any other BSP or direct Meta access) | Highest | Fully sanctioned | Production. Templates approved, webhooks, 24h window honored. |
| WhatsApp Web automation (Playwright) | Medium | Gray area; risk of ban | Only for prototyping or as a fallback for receiving while API approval is pending. |
| WhatsApp Business App (mobile, Linked Devices, ADB) | Low | Gray area | Don't. |
This skill assumes API-via-BSP. Doctrine for Playwright-against-WhatsApp-Web is included as a fallback in §9.4, but it's a fallback, not a recommendation.
After any message a user sends to your business number, you have a 24-hour window during which you can send free-form text in reply. After 24 h of user silence, only Meta-approved templates can be sent — and each template send is billed as a new conversation.
This rule shapes everything:
wa-inboundReactive. Answer messages from leads who wrote first. Runs every 1-2 min during business hours.
wa-outboundProactive. Push the first template to new leads who arrived from a social-media channel (with explicit opt-in). Runs every 5 min, only pulling from the shared lead queue.
wa-followupScheduled relance for leads who went cold. Runs once a day. Uses approved templates only.
Use a lightweight read endpoint as a health check. With the example BSP:
curl -s "https://app.whatchimp.com/api/v1/whatsapp/subscriber/list" \
-d "apiToken=<WA_API_KEY>" \
-d "phone_number_id=<WA_PHONE_NUMBER_ID>" \
-d "limit=1" -d "offset=1"
Expect:
status, data, or equivalent) indicating success.Never auto-rotate API tokens from inside a cron. Token rotation is a user-side action.
WhatsApp has two distinct gating mechanisms:
Per-conversation state:
The cron must check this state before every outbound send. With the example BSP:
# fetch the last conversation message timestamp
curl -s "https://app.whatchimp.com/api/v1/whatsapp/get/conversation" \
-d "apiToken=<WA_API_KEY>" -d "phone_number_id=<WA_PHONE_NUMBER_ID>" \
-d "phone_number=<DIGITS>" -d "limit=1"
If the last sender="user" message is < 24 h ago → free text OK. Otherwise → use a template.
Meta assigns each business number a messaging tier:
New numbers start at Tier 1. Tiers go up automatically based on volume + quality score (low block / report rate). They go down — or get the number paused — if the quality score drops.
Phase A (Tier 1 OR quality score = yellow/red): only inbound replies + follow-ups via approved templates. No new outbound first-contact templates above 50/day.
Phase B (Tier 2+ AND quality score = green): full doctrine.
Always read <WORKSPACE_DIR>/memory/wa-state.md at start.
For a Tier 1 number with a strong external context (verified business, validated opt-in pipeline), you can force higher outbound by appending YYYY-MM-DD - phase=B (manual override) to wa-state.md. Document the rationale in wa-learnings.md. Risk: hitting Tier 1's hard 1000-uniques-per-24h limit + faster quality-score drop on any block.
A message is repliable only if all of:
wa-blacklist.md).If any check fails: skip.
The qualification flow depends on your industry. The pattern below is the structure you should mirror — replace the questions with your own.
For every case type, the goal is the same: collect enough information for a human teammate to make a useful first call.
Decision tree skeleton:
User writes "I have a <problem-type>"
→ Bot asks the 3-5 case-type-specific questions
→ Bot collects: name + phone (already known) + email (if needed)
→ If qualified: alert primary chat, add row to CRM Sheet
→ Hand-off message: "A specialist will call you back. Confirm your name and best callback time?"
Example case-type buckets (adapt to your domain):
wa-clients-known.md): respond empathetically, redirect to the standard client support line, NEVER paste the prospect CTA.If the lead has asked > 5 questions without giving contact info, switch the script:
[Empathy line about the questions]. To answer them precisely, a specialist needs to call you back. Could you confirm your name + best time?
This single message converts more questions-only conversations than any other tactic.
Some BSPs route messages through systems that munge or reject non-ASCII characters silently. If your BSP exhibits this (you'll see your accented characters disappear or be replaced in delivered messages), normalize before sending:
é → e, à → a, etc.).' ' → '), em-dashes (— → -), ellipses (… → ...).This is the WhatsApp equivalent of TikTok's cliclick t: accent trap and Reddit's LC_NUMERIC trap — same family, different symptom. Whether you need it depends on your BSP; test once with a roundtrip ("send accented message → fetch back via API") before assuming you're safe.
[Greeting + brand line]. [How can we help?] [Reassurance about free study, if applicable.]
[Empathy 1 line]. To help precisely, could you tell me [first contextual question]?
Thank you for these details. A specialist from <BRAND_NAME> will call you back as soon as possible. Could you confirm your name and the best time to reach you?
This is outside our specialty. The right place for this is <PARTNER_CHANNEL_OR_APP> — they handle exactly this.
When a lead opts in via another channel (TikTok comment → DM → "send me your phone" → user sends phone), the first WhatsApp message must be a Meta-approved template. Free-form is not allowed when the user has never written to your business number.
Flow:
1. Upstream agent writes lead to <WORKSPACE_DIR>/../shared/leads-whatsapp.json
with status="pending", phone="<DIGITS_NO_PLUS>", source="<platform>",
situation="<short-context>"
2. wa-outbound cron picks the lead up
3. Creates the contact via BSP subscriber/create
4. Sends template <WA_TEMPLATE_FIRST_CONTACT>
5. Updates status="first_message_sent" + adds row to CRM Sheet (append)
6. NEVER sends an alert here — alerts are reserved for the qualification step
Sending an "alert: new lead" to the human team after the first template send creates dozens of false alerts (templates that are never replied to). The doctrine: alert ONLY when the lead replies AND qualifies. This is the single most important anti-doublon rule of the entire skill.
For leads who received the first template but did not reply:
| Day | Template | Tone |
|---|---|---|
| J+1 | <WA_TEMPLATE_FOLLOWUP_SOFT> | Doux. "Still interested?" |
| J+3 | <WA_TEMPLATE_FOLLOWUP_SOFT> | Doux. "We're here to help." |
| J+7 | <WA_TEMPLATE_FOLLOWUP_HARD> | Direct. "Time-sensitive, last call." |
| J+15 | <WA_TEMPLATE_FOLLOWUP_HARD> | Direct. |
| J+20 | <WA_TEMPLATE_CLOSING> | Polite closure. After this, status="lost", no more outreach. |
The cadence above is a recommendation; adapt per niche. The cardinal rule: stop after the closing template. Continuing past J+20 → ban-risk territory.
| Action | Phase A limit (Tier 1) | Phase B limit (Tier 2+) |
|---|---|---|
| Inbound replies / 24 h | unlimited (just answer) | unlimited |
| Inbound replies / cron run | 20 | 50 |
| First-contact templates / 24 h | 50 | 500 |
| Follow-up templates / 24 h | 20 | 200 |
| Unique recipients / 24 h | 800 (well under Tier 1's 1000 cap) | tier cap minus 10 % buffer |
| Conversations / cron run | 30 | 80 |
| Same user — outbound frequency | min 24 h between any two outbound messages |
Quota tracking: read wa-recaps.md + wa-template-log.md at start of every run.
WhatsApp ban risk is dominated by two bad patterns: duplicate alerts to the same human chat (annoying) and duplicate template sends to the same number (catastrophic for quality score).
wa-alerts-sent.md — every time you alert the human team about a qualified lead:
{"phone": "+<DIGITS>", "name": "<Name>", "qualified_date": "YYYY-MM-DD"}.wa-template-log.md — every template send:
{"phone": "+<DIGITS>", "template_id": "<ID>", "sent_at": "<ISO>", "result": "ok|error"}.wa-crm-state.md — current CRM row state per phone:
wa-outbound cron appends. All other crons UPDATE existing rows.Before any outbound action (send, alert, CRM write), check the anti-doublon register. If in doubt, SKIP. It is always better to skip than to send a duplicate.
This single rule is the difference between a number that stays Tier 2 for years and a number that gets paused in 30 days.
Frequency: every 2 min during business hours (24/7 if your niche supports it).
1. Session check (see §2).
2. List subscribers with unseen_count > 0:
POST /whatsapp/subscriber/list apiToken phone_number_id limit=50 orderBy=1
If 0 unread → STOP IMMEDIATELY. No further processing.
3. For each unread subscriber (max 20 per run on Phase A, 50 on Phase B):
a. Fetch the conversation history (last 20 messages):
POST /whatsapp/get/conversation apiToken phone_number_id phone_number limit=20
b. If the most recent sender="bot" (we already replied) → SKIP.
c. Read the full visible history for context.
d. Qualify (see §4).
e. Draft reply.
f. Verify encoding (see §5 "Encoding").
g. Send via the free-form endpoint:
POST /whatsapp/send apiToken phone_number_id phone_number message=<TEXT>
h. Verify response status=1.
4. If a lead is qualified during this run:
a. Read wa-alerts-sent.md — if the phone is there → SKIP alerting.
b. Otherwise: alert primary chat with the structured message:
"QUALIFIED LEAD: <Name> — <case type>. Phone: <number>. Source: <platform>."
Append to wa-alerts-sent.md.
c. UPDATE the CRM row for this phone (no APPEND from wa-inbound).
5. Mandatory recap (see §12).
Frequency: every 5 min.
1. Session check.
2. Read the shared queue: <inbound_pipeline.shared_queue_file>.
3. Filter entries with status="pending".
4. If 0 pending → STOP IMMEDIATELY.
5. For each pending lead:
a. Read wa-template-log.md — if (phone, <WA_TEMPLATE_FIRST_CONTACT>) was sent in the last 24h → SKIP.
b. Create the subscriber (if not already present):
POST /whatsapp/subscriber/create apiToken phoneNumberID name phoneNumber
c. Send the first-contact template:
POST /whatsapp/send/template apiToken phone_number_id phone_number template_id=<WA_TEMPLATE_FIRST_CONTACT>
d. Verify response status=1.
e. Update the shared queue: status="first_message_sent".
f. APPEND a row to CRM Sheet (this is the ONLY cron that APPENDs).
g. Append to wa-template-log.md.
6. Never alert the human team from this cron.
7. Mandatory recap.
Frequency: daily, e.g. 10am.
1. Session check.
2. Read CRM Sheet — find leads with status in {first_message_sent, follow_up_sent} and idle for the relevant J+N day.
3. For each candidate:
a. Check wa-template-log.md anti-doublon.
b. Send the appropriate follow-up template per §6 cadence.
c. Update CRM status + wa-template-log.md.
4. Mandatory recap.
If your BSP is down or pending approval, you may run a temporary Playwright pipeline against web.whatsapp.com. Rules:
Selectors (subject to change):
[data-testid='chat-list'] items.[contenteditable='true'][data-tab='10'].This path is a fallback. Migrate back to BSP-API as soon as approval lands.
| Code | Meaning | Recap action |
|---|---|---|
| 0 | All messages sent / no action needed (zero unread) | status: ok |
| 1 | Fatal error (API 5xx, JSON parse fail) | status: error, alert |
| 2 | 24h window violation attempted (skipped — would have sent free-form to a cold conversation) | status: skip, log |
| 3 | API auth fail (401 / 403) | status: blocked, alert immediately |
| 4 | Anti-doublon SKIP (already alerted / sent) | status: ok |
phoneNumberID vs phone_number_id: some BSPs use different parameter casing on different endpoints (subscriber/create vs send/template). Read your BSP docs carefully — case errors silently 200-OK with no message delivered.+. 33612345678 works, +33612345678 often does not (BSP-dependent).File: <WORKSPACE_DIR>/memory/wa-state.md
File: <WORKSPACE_DIR>/memory/wa-alerts-sent.md
File: <WORKSPACE_DIR>/memory/wa-template-log.md
File: <WORKSPACE_DIR>/memory/wa-crm-state.md
File: <WORKSPACE_DIR>/memory/wa-blacklist.md
File: <WORKSPACE_DIR>/memory/wa-clients-known.md
| Issue | Action |
|---|---|
| HTTP 401 / 403 | Token expired / revoked. Stop. Alert. |
| HTTP 429 | Rate limited. Stop. Wait next run. |
| Template send returns "outside 24h window" | Means you tried free-form on a closed conversation. Switch to template, retry once. |
| Template send returns "template not approved" | Template was rejected or paused by Meta. Stop using it. Pick fallback. |
| Quality score = yellow | Flip to Phase A. Reduce outbound. Audit recent template content. |
| Quality score = red | Stop all outbound. Inbound only. Alert. Manual review of last 7 days. |
| Number paused by Meta | Stop everything. Manual review + appeal via BSP. |
| BSP API 5xx repeatedly | Likely BSP outage. Stop. Switch to fallback (§9.4) only if business-critical. |
| Webhook missing message | Inbound reply gets delayed > 24h window. Document; switch to higher-frequency polling. |
At the end of each cron:
Alert channel — final run message:
[Job name] — [status: ok|partial|blocked|skipped]
Inbound replies: [N or "—"]
Templates sent: [N or "—"]
Qualified leads: [count + names short]
Phase / Tier: [A|B / Tier 1|2|3|4]
Quality score: [green|yellow|red]
Blockers: [text OR "—"]
Next action: [1 line]
Memory — append to <WORKSPACE_DIR>/memory/wa-recaps.md:
## YYYY-MM-DD HH:MM TZ — <job-id> — status: <status>
- Job: <description>
- Phase: A|B
- Tier: 1|2|3|4
- Quality: green|yellow|red
- Inbound replies: <N>
- Templates: <N>
- Qualified leads: <list or "—">
- Blockers: <text or "—">
- Next action: <1 line>
Located at: <WORKSPACE_DIR>/memory/
| File | Purpose | Update cadence |
|---|---|---|
wa-recaps.md | Per-run logs | Every cron run |
wa-state.md | Daily phase / tier / quality | Daily Metrics Recap |
wa-alerts-sent.md | Anti-doublon alert log (one row per qualified lead) | Every qualified-lead event |
wa-template-log.md | Every template send (phone + template + timestamp + result) | Every template send |
wa-crm-state.md | Mirror of CRM Sheet state for anti-doublon | After every CRM update |
wa-blacklist.md | Numbers to NEVER contact (opt-outs, bad actors) | Ad hoc |
wa-clients-known.md | Existing paying clients (no prospect CTA) | Ad hoc |
wa-learnings.md | What worked, what got flagged | Weekly + ad hoc |
wa-incidents.md | Past quality-score drops, paused-number incidents, root causes | When something breaks |
Shared with upstream social-media agents:
<WORKSPACE_DIR>/../shared/leads-whatsapp.json — pending leads to push (written by social agents, read by wa-outbound).When the Daily Metrics Recap detects (tier ≥ 2) AND (quality_score = green) AND (no quality drop in last 14 d):
wa-state.md: YYYY-MM-DD - tier=2 - PHASE_B_THRESHOLD_REACHED.🎉 WhatsApp number ready for Phase B — review outbound caps.status=0 is a failure).Better silence than spam. Better a blockage report than a fake success.
<WORKSPACE_DIR>/memory/ exists with the 9 memory files.<ALERT_CHAT_PRIMARY>, optional <ALERT_CHAT_SECONDARY>) tested with a "hello" message.<inbound_pipeline.shared_queue_file>.A bash one-liner to init the memory files:
mkdir -p "<WORKSPACE_DIR>/memory" && cd "$_" && touch wa-recaps.md wa-state.md wa-alerts-sent.md wa-template-log.md wa-crm-state.md wa-blacklist.md wa-clients-known.md wa-learnings.md wa-incidents.md
And the shared queue:
mkdir -p "$(dirname "<inbound_pipeline.shared_queue_file>")" && echo '[]' > "<inbound_pipeline.shared_queue_file>"
Q: Do I need OpenClaw to use this skill? A: No. OpenClaw is the example agent runtime — the doctrine is BSP-API-driven and works with any agent / runtime that can make HTTP requests + read/write JSON state files.
Q: Which BSP should I pick?
A: Default recommendation: Whatchimp — it's the reference BSP for this skill. It's a Meta Business Partner, charges 0% markup on top of Meta's official messaging fees, exposes a clean REST + webhook surface, manages template approval in-platform, ships a native AI chatbot + shared team inbox + agent routing, and offers an omnichannel inbox covering WA + IG DM + FB Messenger (which composes well with the upstream tiktok-/instagram-/facebook-account-operations skills). If Whatchimp is not available in your region or your stack pushes you elsewhere, the doctrine is provider-agnostic — other tested alternatives: 360Dialog, Twilio, Interakt, MessageBird, Vonage, or direct Meta Cloud API (no BSP layer, more compliance to handle yourself).
Q: Can I use this skill for multiple WhatsApp numbers?
A: Yes — clone the workspace dir per number. Each number gets its own memory/, its own anti-doublon registers, its own quality score. Do NOT share wa-template-log.md across numbers (different rate limits, different cadences).
Q: My number got paused by Meta. What now?
A: Stop everything. Manually review the last 200 actions in wa-template-log.md + wa-recaps.md. Common root causes: (1) outbound to cold leads who never opted in, (2) duplicate templates to same number within 24h, (3) template content drift from Meta's approved version. Appeal via BSP. Do NOT spin up a second number to bypass — Meta cross-checks businesses.
Q: What's the single most important rule of this skill? A: Anti-doublon (§8). One alert per qualified lead, one row per phone, one template per (phone, template_id, 24h-window). This rule alone protects you from the most common quality-score collapses.
Q: Can I send marketing broadcasts? A: Only via Meta-approved MARKETING-category templates, only to users who explicitly opted in, and only at a cadence that respects your tier. Default to "no marketing broadcast" unless you have a clear opt-in pipeline and a quality-score buffer.
Q: What if the BSP webhook is delayed and a user message lands past the 24h window before I see it?
A: You can no longer send free-form. Open the conversation with a templated message (an UTILITY-category template if possible, MARKETING if not) and explain the delay briefly inside the template (template body can include a variable slot for context). Document the incident in wa-incidents.md and audit the webhook latency.