本文へ移動
cccskills
無料GitHub で公開

ops-whatsapp-biz

WhatsApp Business Cloud API — send approved template messages at scale, manage templates with approval tracking, and integrate product catalogs. Separate from wacli personal WhatsApp.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md14.7 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

OPS ► WHATSAPP BUSINESS

WhatsApp Business Cloud API — distinct from wacli personal WhatsApp.

Key difference: This skill uses the WhatsApp Business Cloud API (Meta Graph API) for business-to-customer messaging at scale. wacli is for personal WhatsApp messaging. Different credentials, different phone numbers, different use cases.

Credential Resolution

PREFS_PATH="${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json"

# WhatsApp Business credentials (separate from wacli personal)
WABA_TOKEN="${WHATSAPP_BUSINESS_TOKEN:-$(claude plugin config get whatsapp_business_token 2>/dev/null)}"
WABA_PHONE_ID="${WHATSAPP_PHONE_NUMBER_ID:-$(claude plugin config get whatsapp_phone_number_id 2>/dev/null)}"
WABA_ACCOUNT_ID="${WHATSAPP_BUSINESS_ACCOUNT_ID:-$(claude plugin config get whatsapp_business_account_id 2>/dev/null)}"

# Doppler fallback
if [ -z "$WABA_TOKEN" ]; then
  WABA_TOKEN="$(doppler secrets get WHATSAPP_BUSINESS_TOKEN --plain 2>/dev/null)"
fi
if [ -z "$WABA_PHONE_ID" ]; then
  WABA_PHONE_ID="$(doppler secrets get WHATSAPP_PHONE_NUMBER_ID --plain 2>/dev/null)"
fi
if [ -z "$WABA_ACCOUNT_ID" ]; then
  WABA_ACCOUNT_ID="$(doppler secrets get WHATSAPP_BUSINESS_ACCOUNT_ID --plain 2>/dev/null)"
fi

Credential check: If WABA_TOKEN or WABA_PHONE_ID is empty, print: WhatsApp Business not configured. Run /ops:whatsapp-biz setup to configure credentials. and stop.


Sub-command Routing

Route $ARGUMENTS:

InputAction
(empty), list-templatesList all templates with approval status
send-templateSend an approved template message to one or more recipients
create-templateGuided template creation wizard
check-template <NAME>Poll approval status for a specific template
catalogView and manage linked product catalog
setupConfigure WhatsApp Business API credentials

list-templates

List all message templates for the WhatsApp Business Account.

RESULT=$(curl -s "https://graph.facebook.com/v20.0/${WABA_ACCOUNT_ID}/message_templates?fields=name,status,category,language,components&limit=50" \
  -H "Authorization: Bearer ${WABA_TOKEN}")

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  WHATSAPP BUSINESS TEMPLATES"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
printf "| %-30s | %-10s | %-12s | %-8s |\n" "Template Name" "Status" "Category" "Language"
printf "|%s|%s|%s|%s|\n" "--------------------------------" "------------" "--------------" "----------"

echo "$RESULT" | jq -r '.data[]? | [.name, .status, .category, .language] | @tsv' 2>/dev/null | \
  while IFS=$'\t' read -r name status category language; do
    STATUS_ICON="○"
    [ "$status" = "APPROVED" ] && STATUS_ICON="✓"
    [ "$status" = "REJECTED" ] && STATUS_ICON="✗"
    [ "$status" = "PENDING" ] && STATUS_ICON="…"
    printf "| %-30s | %s %-9s | %-12s | %-8s |\n" "${name:0:30}" "$STATUS_ICON" "$status" "${category:0:12}" "$language"
  done

TOTAL=$(echo "$RESULT" | jq '.data | length // 0')
APPROVED=$(echo "$RESULT" | jq '[.data[]? | select(.status == "APPROVED")] | length')
echo ""
echo "Total: ${TOTAL} templates  |  Approved: ${APPROVED}"
echo ""
echo "Note: Per-template pricing applies since July 2025. Check Meta pricing page for current rates."

send-template

Send an approved template message to one or more recipients.

Before sending, collect via AskUserQuestion:

  1. Template name (free text — use list-templates first to see available)
  2. Recipient phone number(s) — free text (single number or comma-separated list, include country code, e.g. +14155552671)

If template has variables, ask for each parameter value via AskUserQuestion (free text).

Cost note: Always display approximate cost before sending. Utility templates: ~$0.005/msg; Marketing: ~$0.025/msg (US rates — varies by country). Show AskUserQuestion to confirm before bulk sends > 10 recipients.

# Parse recipients
IFS=',' read -ra RECIPIENTS <<< "$PHONE_NUMBERS"
RECIPIENT_COUNT=${#RECIPIENTS[@]}

# For bulk sends > 10, confirm
if [ $RECIPIENT_COUNT -gt 10 ]; then
  # AskUserQuestion: "Send to ${RECIPIENT_COUNT} recipients? Estimated cost: ~$$(awk "BEGIN {printf \"%.2f\", $RECIPIENT_COUNT * 0.025}") for marketing templates." options [Send, Cancel]
  [ "$CONFIRM" = "Cancel" ] && echo "Cancelled." && exit 0
fi

# Send to each recipient
SUCCESS=0; FAILED=0
for PHONE in "${RECIPIENTS[@]}"; do
  PHONE=$(echo "$PHONE" | tr -d ' ')
  
  RESP=$(curl -s -X POST "https://graph.facebook.com/v20.0/${WABA_PHONE_ID}/messages" \
    -H "Authorization: Bearer ${WABA_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "{
      \"messaging_product\": \"whatsapp\",
      \"to\": \"${PHONE}\",
      \"type\": \"template\",
      \"template\": {
        \"name\": \"${TEMPLATE_NAME}\",
        \"language\": {\"code\": \"en_US\"},
        \"components\": ${TEMPLATE_COMPONENTS_JSON:-[]}
      }
    }")
  
  MSG_ID=$(echo "$RESP" | jq -r '.messages[0].id // empty')
  if [ -n "$MSG_ID" ]; then
    SUCCESS=$((SUCCESS + 1))
  else
    FAILED=$((FAILED + 1))
    ERR=$(echo "$RESP" | jq -r '.error.message // "unknown error"')
    echo "  Failed for ${PHONE}: ${ERR}"
  fi
done

echo ""
echo "Sent: ${SUCCESS}/${RECIPIENT_COUNT} messages delivered"
[ $FAILED -gt 0 ] && echo "Failed: ${FAILED} — check phone number format (+country_code + number)"

Template components format (build TEMPLATE_COMPONENTS_JSON from user input):

For templates with header + body variables:

[
  {
    "type": "header",
    "parameters": [{"type": "text", "text": "{{header_value}}"}]
  },
  {
    "type": "body",
    "parameters": [
      {"type": "text", "text": "{{var1}}"},
      {"type": "text", "text": "{{var2}}"}
    ]
  }
]

For templates with no variables: TEMPLATE_COMPONENTS_JSON=[]


create-template

Guided template creation wizard.

Step 1 — Collect basic info:

AskUserQuestion: "Template category?" options [MARKETING, UTILITY, AUTHENTICATION, More...]

If More: AskUserQuestion: [AUTHENTICATION, UTILITY, Skip]

Step 2 — AskUserQuestion: "Template name (lowercase, underscores only, e.g. order_confirmation):" (free text)

Step 3 — AskUserQuestion: "Language?" options [en_US, en_GB, es, fr]

Step 4 — Collect body text (free text). Instruct user: "Use {{1}}, {{2}} etc for variables."

Step 5 — AskUserQuestion: "Add a header?" options [Text header, Image header, No header, Video header]

Step 6 — AskUserQuestion: "Add call-to-action button?" options [URL button, Phone button, No button, Quick reply]

# Build components array
COMPONENTS='[{"type":"BODY","text":"'"${BODY_TEXT}"'"}]'

# Add header if selected
if [ "$HEADER_TYPE" = "Text header" ]; then
  HEADER_TEXT_INPUT="..."  # collected via AskUserQuestion
  COMPONENTS=$(echo "$COMPONENTS" | jq '. + [{"type":"HEADER","format":"TEXT","text":"'"${HEADER_TEXT_INPUT}"'"}]')
elif [ "$HEADER_TYPE" = "Image header" ]; then
  COMPONENTS=$(echo "$COMPONENTS" | jq '. + [{"type":"HEADER","format":"IMAGE","example":{"header_handle":["<upload_handle>"]}}]')
fi

# Add button if selected
if [ "$BUTTON_TYPE" = "URL button" ]; then
  BUTTON_URL="..."  # collected via AskUserQuestion: "Button URL:"
  BUTTON_TEXT="..."  # collected via AskUserQuestion: "Button text:"
  COMPONENTS=$(echo "$COMPONENTS" | jq '. + [{"type":"BUTTONS","buttons":[{"type":"URL","text":"'"${BUTTON_TEXT}"'","url":"'"${BUTTON_URL}"'"}]}]')
elif [ "$BUTTON_TYPE" = "Phone button" ]; then
  BUTTON_PHONE="..."  # collected via AskUserQuestion: "Phone number for button:"
  BUTTON_TEXT="..."
  COMPONENTS=$(echo "$COMPONENTS" | jq '. + [{"type":"BUTTONS","buttons":[{"type":"PHONE_NUMBER","text":"'"${BUTTON_TEXT}"'","phone_number":"'"${BUTTON_PHONE}"'"}]}]')
fi

RESP=$(curl -s -X POST "https://graph.facebook.com/v20.0/${WABA_ACCOUNT_ID}/message_templates" \
  -H "Authorization: Bearer ${WABA_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{
    \"name\": \"${TEMPLATE_NAME}\",
    \"category\": \"${CATEGORY}\",
    \"language\": \"${LANGUAGE}\",
    \"components\": ${COMPONENTS}
  }")

TEMPLATE_ID=$(echo "$RESP" | jq -r '.id // empty')
STATUS=$(echo "$RESP" | jq -r '.status // empty')

if [ -n "$TEMPLATE_ID" ]; then
  echo "Template submitted (ID: ${TEMPLATE_ID}, status: ${STATUS})."
  echo "Approval typically takes 24-48 hours. Check status with: /ops:whatsapp-biz check-template ${TEMPLATE_NAME}"
else
  echo "Template creation failed: $(echo "$RESP" | jq -r '.error.message // "unknown error"')"
fi

check-template <NAME>

Poll approval status for a specific template by name.

TEMPLATE_NAME_ARG=$(echo "$ARGUMENTS" | awk '{print $2}')
if [ -z "$TEMPLATE_NAME_ARG" ]; then
  # AskUserQuestion: "Template name to check:" (free text)
  TEMPLATE_NAME_ARG="$USER_INPUT"
fi

RESULT=$(curl -s "https://graph.facebook.com/v20.0/${WABA_ACCOUNT_ID}/message_templates?name=${TEMPLATE_NAME_ARG}&fields=name,status,rejected_reason,quality_score" \
  -H "Authorization: Bearer ${WABA_TOKEN}")

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  TEMPLATE STATUS: ${TEMPLATE_NAME_ARG}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""

echo "$RESULT" | jq -r '.data[]? | "Status: \(.status)\nQuality: \(.quality_score.score // "N/A")\nRejection reason: \(.rejected_reason // "—")"' 2>/dev/null

STATUS=$(echo "$RESULT" | jq -r '.data[0].status // "NOT_FOUND"')
case "$STATUS" in
  APPROVED)  echo "Template is ready to use with send-template." ;;
  REJECTED)  echo "Template was rejected. Review the rejection reason and create a revised template." ;;
  PENDING)   echo "Template is pending review (typically 24-48h)." ;;
  NOT_FOUND) echo "Template '${TEMPLATE_NAME_ARG}' not found. Run list-templates to see available templates." ;;
esac

catalog

View and manage the product catalog linked to your WhatsApp Business Account.

# List linked catalogs
CATALOGS=$(curl -s "https://graph.facebook.com/v20.0/${WABA_ACCOUNT_ID}?fields=catalog_id" \
  -H "Authorization: Bearer ${WABA_TOKEN}")
CATALOG_ID=$(echo "$CATALOGS" | jq -r '.catalog_id // empty')

if [ -z "$CATALOG_ID" ]; then
  echo "No product catalog linked to this WhatsApp Business Account."
  echo "To link a catalog: go to Meta Business Manager → Commerce Manager → link your catalog to WhatsApp."
  exit 0
fi

# List products in catalog
PRODUCTS=$(curl -s "https://graph.facebook.com/v20.0/${CATALOG_ID}/products?fields=id,name,retailer_id,price,availability,url&limit=20" \
  -H "Authorization: Bearer ${WABA_TOKEN}")

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  WHATSAPP PRODUCT CATALOG"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Catalog ID: ${CATALOG_ID}"
echo ""
printf "| %-25s | %-12s | %-10s | %-12s |\n" "Product Name" "SKU" "Price" "Availability"
printf "|%s|%s|%s|%s|\n" "---------------------------" "--------------" "------------" "--------------"
echo "$PRODUCTS" | jq -r '.data[]? | [.name, (.retailer_id // "—"), (.price // "—"), (.availability // "—")] | @tsv' 2>/dev/null | \
  while IFS=$'\t' read -r name sku price avail; do
    printf "| %-25s | %-12s | %-10s | %-12s |\n" "${name:0:25}" "${sku:0:12}" "$price" "$avail"
  done

PRODUCT_COUNT=$(echo "$PRODUCTS" | jq '.data | length // 0')
echo ""
echo "Total products shown: ${PRODUCT_COUNT} (use catalog in message templates with product cards)"

MM Lite note: Marketing Messages Lite (AI-optimized sends) is a beta API as of 2026. It is not implemented here — check Meta's developer documentation for current availability and enrollment requirements.


setup

Configure WhatsApp Business API credentials.

Before asking, auto-scan for existing credentials:

# Scan env vars
printenv WHATSAPP_BUSINESS_TOKEN WHATSAPP_PHONE_NUMBER_ID WHATSAPP_BUSINESS_ACCOUNT_ID 2>/dev/null

# Scan Doppler
for proj in $(doppler projects --json 2>/dev/null | jq -r '.[].slug'); do
  for cfg in dev stg prd; do
    doppler secrets --project "$proj" --config "$cfg" --json 2>/dev/null | \
      jq -r --arg p "$proj" --arg c "$cfg" 'to_entries[] | select(.key | test("WHATSAPP_BUSINESS|WHATSAPP_PHONE"; "i")) | "\(.key)=\(.value.computed) (doppler:\($p)/\($c))"'
  done
done

# Shell profiles
grep -h 'WHATSAPP_BUSINESS\|WHATSAPP_PHONE' ~/.zshrc ~/.bashrc ~/.zprofile ~/.envrc 2>/dev/null | grep -v '^#'

# Existing plugin config
claude plugin config get whatsapp_business_token 2>/dev/null && echo "✓ whatsapp_business_token already configured"
claude plugin config get whatsapp_phone_number_id 2>/dev/null && echo "✓ whatsapp_phone_number_id already configured"
claude plugin config get whatsapp_business_account_id 2>/dev/null && echo "✓ whatsapp_business_account_id already configured"

If credentials not found, guide user:

  1. AskUserQuestion: "Where do you have your WhatsApp Business credentials?" options [Paste token now, Find in Meta dashboard, Skip]

Where to find credentials in Meta:

  • WHATSAPP_BUSINESS_TOKEN: Meta Developer Portal → Your App → WhatsApp → API Setup → Temporary access token (or generate a permanent System User token)
  • WHATSAPP_PHONE_NUMBER_ID: Same page → "From" phone number → Phone Number ID
  • WHATSAPP_BUSINESS_ACCOUNT_ID: Meta Business Manager → Business Settings → WhatsApp Accounts → Account ID
  1. Collect each value via AskUserQuestion (free text):

    • WhatsApp Business Token
    • Phone Number ID
    • Business Account ID (WABA ID)
  2. Save via plugin config:

claude plugin config set whatsapp_business_token "$WABA_TOKEN"
claude plugin config set whatsapp_phone_number_id "$WABA_PHONE_ID"
claude plugin config set whatsapp_business_account_id "$WABA_ACCOUNT_ID"
  1. Smoke test:
TEST=$(curl -s "https://graph.facebook.com/v20.0/${WABA_PHONE_ID}" \
  -H "Authorization: Bearer ${WABA_TOKEN}")
NAME=$(echo "$TEST" | jq -r '.display_phone_number // empty')
if [ -n "$NAME" ]; then
  echo "WhatsApp Business ✓ connected — Phone: ${NAME}"
else
  echo "WhatsApp Business ✗ connection failed: $(echo "$TEST" | jq -r '.error.message // "invalid token or phone ID"')"
fi

Report: WhatsApp Business ✓ connected or ✗ invalid — [error].

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Automate ActiveCampaign tasks via Rube MCP (Composio): manage contacts, tags, list subscriptions, automation enrollment, and tasks. Always search tools first for current schemas.

日本語の概要は準備中です。原文の説明を表示しています。

davepoon/buildwithclaude3,6302026年10月12日 更新

Analytics your AI agent can actually use. Track, analyze, run A/B experiments, and optimize across all your projects via CLI. Includes a growth playbook so your agent knows HOW to grow, not just what to track.

日本語の概要は準備中です。原文の説明を表示しています。

davepoon/buildwithclaude3,6302026年10月12日 更新

Teaches when to recall from long-term memory before acting and when to save durable decisions, corrections and failures afterwards. Use when a memory tool or MCP memory server is connected but the agent is not using it consistently, when the user complains that the assistant forgets preferences, conventions or past decisions between sessions, or when setting up persistent memory for a project. Works with any memory backend: a folder of Markdown files, a local MCP server, or a managed service.

日本語の概要は準備中です。原文の説明を表示しています。

davepoon/buildwithclaude3,6302026年10月12日 更新

Audit local AI coding-agent sessions with agenttrace for cost, tokens, tool failures, latency, anomalies, health, diffs, and CI gates.

日本語の概要は準備中です。原文の説明を表示しています。

davepoon/buildwithclaude3,6302026年10月12日 更新

Audit whether a website can be found, crawled, and cited by AI answer engines such as ChatGPT Search, Perplexity, Google AI Overviews, and Microsoft Copilot. Use when someone asks why their brand is missing from AI answers, whether AI crawlers can read their site, how to get cited by ChatGPT or Perplexity, or asks for a GEO or AEO (generative / answer engine optimization) review. Produces a citation baseline across buyer-intent prompts, a crawler-access check, a citability review of named pages, and a ranked fix list. Not for keyword rank tracking, paid search, or pages behind a login.

日本語の概要は準備中です。原文の説明を表示しています。

davepoon/buildwithclaude3,6302026年10月12日 更新

Automate Airtable tasks via Rube MCP (Composio): records, bases, tables, fields, views. Always search tools first for current schemas.

日本語の概要は準備中です。原文の説明を表示しています。

davepoon/buildwithclaude3,6302026年10月12日 更新

davepoon のスキルをすべて見る

このスキルの問題を報告する