### Install OpenCLaw WhatsApp Skills Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-send/README.md Install the whatsapp-send skill by cloning the repository and creating a symbolic link. ```bash cd ~/.openclaw/workspace git clone https://github.com/InitechSoftware/openclaw-whatsapp-skills.git ln -s $(pwd)/openclaw-whatsapp-skills/skills/whatsapp-send ~/.openclaw/skills/ ``` -------------------------------- ### Label String Convention Examples Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/docs/state-persistence.md Examples demonstrating recommended conventions for label strings, including forward-slash namespacing, lowercase-kebab-case, and boolean-style flags. ```Text discovery/q1 ``` ```Text intent/sales ``` ```Text priority/high ``` ```Text needs-human ``` ```Text pause-bot ``` ```Text to-follow-up ``` ```Text qualified ``` ```Text vip ``` -------------------------------- ### Install whatsapp-delivery-check Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-delivery-check/README.md Commands to clone the repository and link the skill into the local OpenClaw workspace. ```bash cd ~/.openclaw/workspace git clone https://github.com/InitechSoftware/openclaw-whatsapp-skills.git ln -s $(pwd)/openclaw-whatsapp-skills/skills/whatsapp-delivery-check ~/.openclaw/skills/ ``` -------------------------------- ### Install WhatsApp Lead Qualifier Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-lead-qualifier/README.md Commands to clone the repository and link the skill to the local OpenClaw workspace. ```bash cd ~/.openclaw/workspace git clone https://github.com/InitechSoftware/openclaw-whatsapp-skills.git ln -s $(pwd)/openclaw-whatsapp-skills/skills/whatsapp-lead-qualifier ~/.openclaw/skills/ ``` -------------------------------- ### Clone OpenClaw WhatsApp Skills Repo Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/README.md Clone the repository into your OpenClaw workspace to get started. ```bash cd ~/.openclaw/workspace git clone https://github.com/InitechSoftware/openclaw-whatsapp-skills.git ``` -------------------------------- ### Define API Placeholders Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/guide/capabilities.md Configuration variables used throughout the integration examples. ```text Your business number : +1 555 0100 (JID: 15550100@s.whatsapp.net) Your customer's number : +1 555 0200 API token : $TIMELINES_AI_API_KEY ``` -------------------------------- ### Worked Example: Routing Customers to EU/US Numbers Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/docs/multi-number.md This example demonstrates how TimelinesAI routes incoming chats to specific WhatsApp numbers based on customer interaction and how bots, configured with `ALLOWED_SENDER_JID`, process these messages. Each bot verifies ownership against its own JID, ensuring only the relevant bot responds. ```text Workspace has: - +1 555 0100 (US support) → 15550100@s.whatsapp.net - +44 20 7000 0300 (EU support) → 442070000300@s.whatsapp.net Incoming chat from a UK customer: - TimelinesAI routes the inbound to +44 20 7000 0300 (based on how the customer addressed the number — they messaged the UK number). - The chat's whatsapp_account_id is "442070000300@s.whatsapp.net". Your EU bot is running with ALLOWED_SENDER_JID=442070000300@s.whatsapp.net. - It sees the webhook, reads the chat, verifies ownership matches, sends. Your US bot is also running with ALLOWED_SENDER_JID=15550100@s.whatsapp.net. - It also sees the webhook (same workspace, same webhook endpoint). - It reads the chat, sees ownership doesn't match its ALLOWED JID, exits. Only one bot replies. No persona leak. ``` -------------------------------- ### Configure TimelinesAI Environment Variables Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/README.md Copy the example .env file and fill in your TimelinesAI API token and allowed sender JID. ```bash cp openclaw-whatsapp-skills/.env.example .env.timelinesai $EDITOR .env.timelinesai ``` -------------------------------- ### Install OpenClaw skills via CLI Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/guide/capabilities.md Clones the companion repository and creates symbolic links for specific skills in the OpenClaw workspace. ```bash cd ~/.openclaw/workspace git clone https://github.com/InitechSoftware/openclaw-whatsapp-skills.git ln -s $(pwd)/openclaw-whatsapp-skills/skills/whatsapp-autoresponder ~/.openclaw/skills/ ln -s $(pwd)/openclaw-whatsapp-skills/skills/whatsapp-lead-qualifier ~/.openclaw/skills/ ln -s $(pwd)/openclaw-whatsapp-skills/skills/whatsapp-send ~/.openclaw/skills/ ln -s $(pwd)/openclaw-whatsapp-skills/skills/whatsapp-delivery-check ~/.openclaw/skills/ ``` -------------------------------- ### Implement Durable Queueing Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/examples/vercel-webhook-receiver/README.md Example of replacing a direct fetch with a durable queue push using QStash to prevent event loss. ```javascript // Instead of `await fetch(OPENCLAW_HOOK_URL, ...)`: await fetch("https://qstash.upstash.io/v2/publish/" + encodeURIComponent(OPENCLAW_HOOK_URL), { method: "POST", headers: { "Authorization": `Bearer ${process.env.QSTASH_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(payload), }); ``` -------------------------------- ### Query Chats by Label Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/docs/state-persistence.md TimelinesAI allows filtering chats based on assigned labels, which is useful for auditing and searching. These examples demonstrate how to query for qualified leads, chats in a specific stage, or those escalated to a human. ```bash # All qualified leads GET /chats?label=qualified ``` ```bash # All chats currently in discovery/q3 GET /chats?label=discovery/q3 ``` ```bash # All chats that escalated to a human GET /chats?label=escalate ``` -------------------------------- ### FAQ Handler with Human Escalation Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/guide/capabilities.md This skill answers common questions and escalates unrecognized queries to a human. Before replying, it checks for 'needs-human' or 'escalate' labels using GET /chats/{chat_id}/labels. If no FAQ match, it adds the 'needs-human' label via POST /chats/{chat_id}/labels. ```python GET /chats/{chat_id}/labels ``` ```python POST /chats/{chat_id}/labels {"label":"needs-human"} ``` -------------------------------- ### GET /integrations/api/whatsapp_accounts Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Retrieves all WhatsApp numbers connected to your TimelinesAI workspace, including JID, phone number, and connection status. ```APIDOC ## GET /integrations/api/whatsapp_accounts ### Description Retrieves all WhatsApp numbers connected to your TimelinesAI workspace. Each number includes its JID (WhatsApp account ID), phone number, connection status, and display name. ### Method GET ### Endpoint https://app.timelines.ai/integrations/api/whatsapp_accounts ### Response #### Success Response (200) - **whatsapp_accounts** (array) - List of connected WhatsApp accounts #### Response Example { "status": "ok", "data": { "whatsapp_accounts": [ { "id": "15550100@s.whatsapp.net", "phone": "+15550100", "status": "active", "owner_name": "Support Team", "account_name": "Support" } ] } } ``` -------------------------------- ### Get Chat Details and Verify Sender - Bash Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Fetches details of a specific chat, including its owner's JID. Essential for multi-number workspaces to verify sender ownership before replying to prevent sending from the wrong number. ```bash # Get chat details including ownership JID CHAT_ID=12345678 CHAT_DETAILS=$(curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID") # Extract the owning JID for sender verification CHAT_JID=$(echo "$CHAT_DETAILS" | jq -r '.data.whatsapp_account_id') echo "Chat owned by: $CHAT_JID" # Verify ownership before sending (multi-number safety) if [ "$CHAT_JID" != "$ALLOWED_SENDER_JID" ]; then echo "Chat $CHAT_ID owned by $CHAT_JID, not $ALLOWED_SENDER_JID — refusing to send" exit 0 fi ``` -------------------------------- ### Get Chat Labels and Check Stop-Reply - Bash Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Retrieves all labels attached to a chat. Checks for stop-reply labels like `needs-human`, `escalate`, or `pause-bot` before sending a reply to respect human takeover or bot pauses. Also extracts the current discovery stage. ```bash # Get labels for a chat CHAT_ID=12345678 LABELS=$(curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/labels" \ | jq -r '.data.labels[]? // empty') # Check for stop-reply labels before sending case "$LABELS" in *needs-human*|*escalate*|*pause-bot*) echo "Stop-reply label present — exiting without reply" exit 0 ;; esac # Find the current discovery stage STAGE=$(echo "$LABELS" | grep -oE 'discovery/q[123]' | head -1) echo "Current stage: ${STAGE:-none}" ``` -------------------------------- ### GET /messages/{uid}/status_history Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/guide/capabilities.md Check the delivery status history of an outbound message. ```APIDOC ## GET /messages/{uid}/status_history ### Description Retrieve the Sent, Delivered, and Read timeline for an outbound message. ### Method GET ### Endpoint /messages/{uid}/status_history ### Parameters #### Path Parameters - **uid** (string) - Required - The unique identifier of the message. ``` -------------------------------- ### GET /chats Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/guide/capabilities.md Retrieve the list of chats. Supports filtering by phone number or label. ```APIDOC ## GET /chats ### Description Retrieve the list of chats. Supports filtering by phone number or label. ### Method GET ### Endpoint /chats ### Parameters #### Query Parameters - **phone** (string) - Optional - Filter chats by phone number. - **label** (string) - Optional - Filter chats by label. ``` -------------------------------- ### Initiate Conversation - First Contact Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-lead-qualifier/SKILL.md If no stage is set, tags the chat with 'discovery/q1' and sends the first question. ```bash if [ -z "$STAGE" ]; then # Add label python3 -c "import json; json.dump({'label': 'discovery/q1'}, open('/tmp/wa_label.json','w'))" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/wa_label.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/labels" # Ask question 1 python3 -c "import json; json.dump({'text': 'Hi! To help you best, can I ask — what are you trying to solve?'}, open('/tmp/wa_msg.json','w'), ensure_ascii=False)" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/wa_msg.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/messages" exit 0 fi ``` -------------------------------- ### GET /integrations/api/chats/{chat_id}/labels Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Retrieves all labels attached to a specific chat, used for stage tracking and routing. ```APIDOC ## GET /integrations/api/chats/{chat_id}/labels ### Description Retrieves all labels attached to a chat. Labels serve as discrete stage markers, routing tags, and stop-reply flags. ### Method GET ### Endpoint https://app.timelines.ai/integrations/api/chats/{chat_id}/labels ### Parameters #### Path Parameters - **chat_id** (string) - Required - The unique identifier of the chat ### Response #### Success Response (200) - **labels** (array) - List of labels attached to the chat ``` -------------------------------- ### GET /integrations/api/chats/{chat_id} Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Fetches full details of a specific chat, including the whatsapp_account_id for sender ownership verification. ```APIDOC ## GET /integrations/api/chats/{chat_id} ### Description Fetches full details of a specific chat including its whatsapp_account_id (the JID of the WhatsApp number that owns the chat). ### Method GET ### Endpoint https://app.timelines.ai/integrations/api/chats/{chat_id} ### Parameters #### Path Parameters - **chat_id** (string) - Required - The unique identifier of the chat ### Response #### Success Response (200) - **whatsapp_account_id** (string) - The JID of the WhatsApp number that owns the chat ``` -------------------------------- ### Symlink OpenClaw WhatsApp Skills Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/README.md Create symbolic links for the skill directories into your OpenClaw skills path. ```bash ln -s $(pwd)/openclaw-whatsapp-skills/skills/whatsapp-autoresponder ~/.openclaw/skills/ ln -s $(pwd)/openclaw-whatsapp-skills/skills/whatsapp-lead-qualifier ~/.openclaw/skills/ ln -s $(pwd)/openclaw-whatsapp-skills/skills/whatsapp-send ~/.openclaw/skills/ ln -s $(pwd)/openclaw-whatsapp-skills/skills/whatsapp-delivery-check ~/.openclaw/skills/ ``` -------------------------------- ### Monitor Logs Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/examples/vercel-webhook-receiver/README.md Commands to view logs for the Vercel deployment and the OpenClaw host. ```bash # Vercel logs vercel logs # OpenClaw host logs (depends on your host setup) ``` -------------------------------- ### Clone OpenClaw WhatsApp Skills Repository Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-autoresponder/README.md Clone the repository into your OpenClaw workspace and create a symbolic link for the whatsapp-autoresponder skill. ```bash cd ~/.openclaw/workspace git clone https://github.com/InitechSoftware/openclaw-whatsapp-skills.git ln -s $(pwd)/openclaw-whatsapp-skills/skills/whatsapp-autoresponder ~/.openclaw/skills/ ``` -------------------------------- ### Advance Stage with Label in TimelinesAI Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Uses Python to create a JSON file with the next stage label and then sends it to a specific chat using curl. Ensure the NEXT_LABEL variable is set. ```bash python3 -c "import json,sys; json.dump({'label': sys.argv[1]}, open('/tmp/label.json','w'))" "$NEXT_LABEL" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/label.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/labels" ``` -------------------------------- ### Process Answer and Advance Stage Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-lead-qualifier/SKILL.md Parses the incoming text as an answer, stores it as a note, and determines the next question and stage label. ```bash case "$STAGE" in "discovery/q1") # Parse $TEXT as use_case. Here we just store it raw; a smarter skill would # run OpenClaw's reasoning to extract structured intent. NOTE_TEXT="[LEAD] use_case=$TEXT" NEXT_LABEL="discovery/q2" NEXT_Q="Got it. How big is your team?" ;; "discovery/q2") NOTE_TEXT="[LEAD] team_size=$TEXT" NEXT_LABEL="discovery/q3" NEXT_Q="Perfect. When are you hoping to start?" ;; "discovery/q3") NOTE_TEXT="[LEAD] timeline=$TEXT" NEXT_LABEL="" # will be set to qualified/disqualified in step 5 NEXT_Q="" ;; esac ``` -------------------------------- ### Get Recent Messages and Notes Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Fetches recent messages from a chat and filters them using jq. Use this to retrieve message history and extract specific data like customer messages or agent notes. ```bash CHAT_ID=12345678 MESSAGES=$(curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/messages?limit=50") ``` ```bash echo "$MESSAGES" | jq '.data.messages[] | select(.from_me==false) | {text, sender_phone, timestamp}' ``` ```bash echo "$MESSAGES" | jq '.data.messages[] | select(.message_type=="note") | .text' ``` ```bash TEAM_SIZE=$(echo "$MESSAGES" | jq -r '.data.messages[] | select(.message_type=="note") | .text' \ | grep -oE 'team_size=[0-9]+' | cut -d= -f2) echo "Team size: $TEAM_SIZE" ``` -------------------------------- ### Qualify Leads with Question Sequence Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/guide/capabilities.md This skill qualifies leads by asking a sequence of questions across multiple turns, storing answers as notes, and applying a 'qualified' tag based on criteria. It uses labels to track the current question stage (e.g., 'discovery/q1') and notes to store answers via POST /chats/{chat_id}/notes. ```python GET /chats/{chat_id}/labels ``` ```python POST /chats/{chat_id}/notes {...} ``` -------------------------------- ### Send Next Question via Message in TimelinesAI Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Uses Python to create a JSON file with the next question text and then sends it as a message to a specific chat using curl. Ensure the NEXT_Q variable is set. ```bash python3 -c "import json,sys; json.dump({'text': sys.argv[1]}, open('/tmp/msg.json','w'), ensure_ascii=False)" "$NEXT_Q" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/msg.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/messages" ``` -------------------------------- ### Read Notes from Chat Messages Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/docs/state-persistence.md Notes are not directly accessible via a dedicated API endpoint. Instead, retrieve them by listing chat messages and filtering for those with `message_type == "note"`. This example uses `curl` and `jq` to filter. ```bash curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/messages?limit=50" \ | jq '.data.messages[] | select(.message_type=="note")' ``` -------------------------------- ### WhatsApp Lead Qualifier Skill Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt A multi-turn lead qualification skill that uses chat labels for state management. It asks a sequence of questions, stores answers as chat notes, and applies a final qualification label based on a rule. State persists across restarts. ```bash #!/bin/bash # Lead qualifier skill - multi-turn state machine using labels and notes CHAT_ID=$1 TEXT=$2 # Customer's incoming message (answer to current question) # Step 1: Read current stage from labels LABELS=$(curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/labels" \ | jq -r '.data.labels[]? // empty') # Exit if already finished or human took over case "$LABELS" in *qualified*|*disqualified*|*escalate*|*needs-human*) exit 0 ;; esac STAGE=$(echo "$LABELS" | grep -oE 'discovery/q[123]' | head -1) # Step 2: Route based on stage if [ -z "$STAGE" ]; then # Fresh lead - start discovery python3 -c "import json; json.dump({'label': 'discovery/q1'}, open('/tmp/label.json','w'))" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/label.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/labels" python3 -c "import json; json.dump({'text': 'Hi! What are you trying to solve?'}, open('/tmp/msg.json','w'), ensure_ascii=False)" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/msg.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/messages" exit 0 fi # Step 3: Process answer and advance case "$STAGE" in "discovery/q1") NOTE="[LEAD] use_case=$TEXT" NEXT_LABEL="discovery/q2" NEXT_Q="Got it. How big is your team?" ;; "discovery/q2") NOTE="[LEAD] team_size=$TEXT" NEXT_LABEL="discovery/q3" NEXT_Q="Perfect. When are you hoping to start?" ;; "discovery/q3") NOTE="[LEAD] timeline=$TEXT" # Apply qualification rule: team_size >= 5 TEAM_SIZE=$(curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/messages?limit=50" \ | jq -r '.data.messages[] | select(.message_type=="note") | .text' \ | grep -oE 'team_size=[0-9]+' | cut -d= -f2) if [ "${TEAM_SIZE:-0}" -ge 5 ]; then NEXT_LABEL="qualified" NEXT_Q="Great fit! Someone will reach out within one business day." else NEXT_LABEL="disqualified" NEXT_Q="Thanks! We're focused on teams of 5+ right now." fi ;; esac ``` -------------------------------- ### State Persistence: Write Structured Data as Note Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Stores structured data (e.g., '[LEAD] team_size=15') as a note for a chat using Python and curl. This is part of a pattern for multi-turn skills without an external database. ```bash # WRITE: Store structured data as a note python3 -c "import json; json.dump({'text': '[LEAD] team_size=15'}, open('/tmp/note.json','w'))" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/note.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/notes" ``` -------------------------------- ### Test whatsapp-autoresponder Skill with Dry Run Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-autoresponder/README.md Set environment variables for API key, allowed sender JID, and chat ID to perform a dry run of the skill. This invokes the skill with a synthetic payload without sending an actual reply, allowing verification of the JID and label checks. ```bash # Dry-run: invoke the skill with a synthetic payload without actually sending export TIMELINES_AI_API_KEY=sk-... export ALLOWED_SENDER_JID=15550100@s.whatsapp.net export CHAT_ID=12345678 export DRY_RUN=1 # Then watch the skill output for the verify + label steps without the send ``` -------------------------------- ### Deploy Vercel Webhook Receiver Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/examples/vercel-webhook-receiver/README.md Commands to clone the repository, generate a secret, and deploy the function to Vercel with required environment variables. ```bash # 1. Clone this repo and cd into the receiver git clone https://github.com/InitechSoftware/openclaw-whatsapp-skills.git cd openclaw-whatsapp-skills/examples/vercel-webhook-receiver # 2. Generate a random webhook secret export WEBHOOK_SECRET=$(openssl rand -hex 16) echo "Your webhook secret (save this): $WEBHOOK_SECRET" # 3. Deploy to Vercel vercel deploy --prod # First run will ask you to link to a Vercel project — accept the defaults or # create a new one. Note the production URL printed at the end. # 4. Set environment variables on the Vercel project vercel env add OPENCLAW_HOOK_URL production # your OpenClaw host URL vercel env add WEBHOOK_SECRET production # paste the secret from step 2 vercel env add ALLOWED_SENDER_JID production # e.g. 15550100@s.whatsapp.net # 5. Redeploy so the new env vars take effect vercel deploy --prod ``` -------------------------------- ### Read Current Chat Stage and Labels Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-lead-qualifier/SKILL.md Fetches chat labels to determine the current stage. Exits if the chat is already qualified, disqualified, or escalated. ```bash LABELS=$(curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/labels" \ | jq -r '.data.labels[]? // empty') # Stop entirely if already finished or handed off to a human case "$LABELS" in *qualified*|*disqualified*|*escalate*|*needs-human*|*pause-bot*) echo "lead already resolved or paused — exiting" exit 0 ;; esac # Find the current discovery stage label STAGE=$(echo "$LABELS" | grep -oE 'discovery/q[123]' | head -1) ``` -------------------------------- ### List connected WhatsApp accounts Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/docs/multi-number.md Retrieve all connected WhatsApp accounts to identify the correct JID for the skill. ```bash curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ https://app.timelines.ai/integrations/api/whatsapp_accounts ``` -------------------------------- ### Add Next Stage Label Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-lead-qualifier/SKILL.md Applies a label for the next stage of a WhatsApp skill flow if it's not the final question. It uses Python to create a JSON file with the label and cURL to send it to the Timelines.ai API. ```bash if [ -n "$NEXT_LABEL" ]; then python3 -c "import json,sys; json.dump({'label': sys.argv[1]}, open('/tmp/wa_label.json','w'))" "$NEXT_LABEL" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/wa_label.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/labels" fi ``` -------------------------------- ### State Persistence: Write Stage as Label Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Stores a discrete stage marker (e.g., 'discovery/q2') as a label for a chat using Python and curl. This is part of a pattern for multi-turn skills without an external database. ```bash # State persistence pattern for multi-turn WhatsApp skills CHAT_ID=12345678 # WRITE: Store discrete stage as a label python3 -c "import json; json.dump({'label': 'discovery/q2'}, open('/tmp/label.json','w'))" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/label.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/labels" ``` -------------------------------- ### Define allowed sender JID Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/docs/multi-number.md Set the environment variable to the specific JID the skill is authorized to use. ```bash # in your .env or your OpenClaw workspace env ALLOWED_SENDER_JID=15550100@s.whatsapp.net ``` -------------------------------- ### Store Answer as Note in TimelinesAI Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Uses Python to create a JSON file with the answer text and then sends it as a note to a specific chat using curl. Ensure the NOTE variable is set. ```bash python3 -c "import json,sys; json.dump({'text': sys.argv[1]}, open('/tmp/note.json','w'), ensure_ascii=False)" "$NOTE" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/note.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/notes" ``` -------------------------------- ### Reply to Existing Chat Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/guide/capabilities.md Send a follow-up message to an existing chat session using the chat ID. ```bash $ cat > /tmp/reply.json <<'JSON' {"text":"Estimated delivery is 2-3 business days. You'll get tracking updates to this chat."} JSON $ curl -sS -X POST \ -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" \ --data-binary @/tmp/reply.json \ https://app.timelines.ai/integrations/api/chats/12345678/messages {"status":"ok","data":{"message_uid":"REPLY-UID-PLACEHOLDER"}} ``` -------------------------------- ### Send Next Question or Apply Qualification Rule Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-lead-qualifier/SKILL.md Sends the next question in a WhatsApp skill flow or applies a qualification rule if it's the final question. It uses Python to format messages and cURL to interact with the Timelines.ai API for sending messages and labels. ```bash if [ -n "$NEXT_Q" ]; then python3 -c "import json,sys; json.dump({'text': sys.argv[1]}, open('/tmp/wa_msg.json','w'), ensure_ascii=False)" "$NEXT_Q" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/wa_msg.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/messages" else # Last question done — fetch all answers and apply qualification rule MESSAGES=$(curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/messages?limit=50") # Extract team_size from the notes. In practice, parse more carefully. TEAM_SIZE=$(echo "$MESSAGES" | jq -r '.data.messages[] | select(.message_type=="note") | .text' | grep -oE 'team_size=[0-9]+' | tr -d 'team_size=') if [ -n "$TEAM_SIZE" ] && [ "$TEAM_SIZE" -ge 5 ]; then FINAL_LABEL="qualified" CLOSING="Thanks! Based on what you've told me, this is a great fit — I'll have someone on our team reach out within one business day." else FINAL_LABEL="disqualified" CLOSING="Thanks for sharing! Unfortunately we're focused on teams of 5+ right now — I'll keep your info on file and reach out if that changes." fi # Tag + send closing python3 -c "import json,sys; json.dump({'label': sys.argv[1]}, open('/tmp/wa_label.json','w'))" "$FINAL_LABEL" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/wa_label.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/labels" python3 -c "import json,sys; json.dump({'text': sys.argv[1]}, open('/tmp/wa_msg.json','w'), ensure_ascii=False)" "$CLOSING" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/wa_msg.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/messages" fi ``` -------------------------------- ### Register Webhook with TimelinesAI Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/examples/vercel-webhook-receiver/README.md API request to register the Vercel webhook URL with the TimelinesAI integration service. ```bash WEBHOOK_URL="https://your-project.vercel.app/api/webhook?secret=${WEBHOOK_SECRET}" curl -sS -X POST \ -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"event_type\":\"message:received:new\",\"url\":\"${WEBHOOK_URL}\",\"enabled\":true}" \ https://app.timelines.ai/integrations/api/webhooks ``` -------------------------------- ### Register TimelinesAI webhook Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/guide/capabilities.md Uses cURL to register an HTTPS endpoint for receiving incoming WhatsApp message events. ```bash curl -sS -X POST \ -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" \ -d '{"event_type":"message:received:new","url":"https://your-app.example.com/webhook","enabled":true}' \ https://app.timelines.ai/integrations/api/webhooks ``` -------------------------------- ### State Persistence: Read Current Stage from Labels Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Retrieves all labels for a chat using curl and then uses jq to extract the most recent stage marker. Assumes labels follow a pattern like 'discovery/q[123]'. ```bash # READ: Get current stage from labels LABELS=$(curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/labels" \ | jq -r '.data.labels[]') STAGE=$(echo "$LABELS" | grep -oE 'discovery/q[123]' | tail -1) ``` -------------------------------- ### Detect and Match Customer Language Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/guide/capabilities.md This skill detects the customer's language and responds in the same language, adapting if the customer switches mid-conversation. This is handled server-side by OpenCLaw, with TimelinesAI relaying the replies. -------------------------------- ### Route Conversations by Intent Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/guide/capabilities.md This skill classifies incoming chats by intent and routes them to the appropriate teammate. It tags chats with an intent (e.g., 'intent/sales') using POST /chats/{chat_id}/labels and assigns responsibility via PATCH /chats/{chat_id}. ```python POST /chats/{chat_id}/labels {"label":"intent/sales"} ``` ```python PATCH /chats/{chat_id} {"responsible_email": "alex@ours.example"} ``` -------------------------------- ### Prepare WhatsApp Message Payload (Chat Mode) Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-send/SKILL.md Writes a JSON payload for sending a WhatsApp message to an existing chat by chat ID to a file. Ensures UTF-8 safety for non-ASCII characters. Requires TEXT environment variable. ```python import json, sys json.dump({'text': sys.argv[1]}, open('/tmp/wa_send.json','w'), ensure_ascii=False) ``` -------------------------------- ### Prepare WhatsApp Message Payload (Phone Mode) Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-send/SKILL.md Writes a JSON payload for sending a WhatsApp message by phone number to a file. Ensures UTF-8 safety for non-ASCII characters. Requires PHONE and TEXT environment variables. ```python import json, sys json.dump({'phone': sys.argv[1], 'text': sys.argv[2]}, open('/tmp/wa_send.json','w'), ensure_ascii=False) ``` -------------------------------- ### Compose and Send WhatsApp Reply using Python and cURL Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-autoresponder/SKILL.md Composes a reply text, saves it to a JSON file with UTF-8 encoding, and sends it to the specified chat using the TimelinesAI API. Ensure to use ensure_ascii=False for proper UTF-8 handling. ```python import json,sys json.dump({'text': sys.argv[1]}, open('/tmp/wa_reply.json','w'), ensure_ascii=False) ``` ```bash REPLY_TEXT="Your composed reply here." python3 -c "import json,sys; json.dump({'text': sys.argv[1]}, open('/tmp/wa_reply.json','w'), ensure_ascii=False)" \ "$REPLY_TEXT" curl -sS -X POST \ -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" \ --data-binary @/tmp/wa_reply.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/messages" ``` -------------------------------- ### Verify chat ownership before sending Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/docs/multi-number.md Check the chat's whatsapp_account_id against the allowed JID to prevent unauthorized persona usage. ```bash CHAT_JID=$(curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID" \ | jq -r '.data.whatsapp_account_id') if [ "$CHAT_JID" != "$ALLOWED_SENDER_JID" ]; then echo "chat $CHAT_ID owned by $CHAT_JID, not $ALLOWED_SENDER_JID — refusing to send" exit 0 fi ``` -------------------------------- ### Register Webhook for Events Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Registers a URL to receive real-time events from TimelinesAI, such as new incoming messages. A secret path segment is recommended for authentication. ```bash # Register webhook URL # WEBHOOK_URL="https://your-domain.com/your-secret-path" # curl -sS -X POST \ # -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ # -H "Content-Type: application/json" \ # --data-binary "{\"url\": \"$WEBHOOK_URL\"}" \ # "https://app.timelines.ai/integrations/api/webhooks" ``` -------------------------------- ### List Registered Webhooks Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Retrieves a list of all currently registered webhooks for the TimelinesAI integration. Requires an API key for authentication. ```bash # List all registered webhooks curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/webhooks" ``` -------------------------------- ### List Connected WhatsApp Numbers - Bash Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Retrieves all WhatsApp numbers connected to your TimelinesAI workspace. Use this to find the correct `ALLOWED_SENDER_JID` for multi-number workspaces. ```bash # List all connected WhatsApp accounts curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/whatsapp_accounts" ``` ```json # Response: # { # "status": "ok", # "data": { # "whatsapp_accounts": [ # { # "id": "15550100@s.whatsapp.net", # "phone": "+15550100", # "status": "active", # "owner_name": "Support Team", # "account_name": "Support" # }, # { # "id": "15550200@s.whatsapp.net", # "phone": "+15550200", # "status": "active", # "owner_name": "Sales Team", # "account_name": "Sales" # } # ] # } # } ``` -------------------------------- ### Multi-Turn Lead Qualifier - Turn 1 Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/docs/state-persistence.md The first turn of a lead qualification flow. The agent interprets the customer's initial message, writes a label, and sends a greeting. ```Text Agent reads labels → (none) Agent interpretation → fresh lead Agent writes label → discovery/q1 Agent sends message → "Hi! What are you trying to solve?" ``` -------------------------------- ### Implement WhatsApp Delivery Check with cURL Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-delivery-check/SKILL.md Use this cURL command to poll the message status history from the TimelinesAI API. Ensure your `TIMELINES_AI_API_KEY` is set in your environment. ```bash curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/messages/$MESSAGE_UID/status_history" ``` -------------------------------- ### Persist Answer and Update Chat Label Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/skills/whatsapp-lead-qualifier/SKILL.md Writes the collected answer as a note on the chat and adds the label for the next stage. ```bash # Write the answer as a note python3 -c "import json,sys; json.dump({'text': sys.argv[1]}, open('/tmp/wa_note.json','w'), ensure_ascii=False)" "$NOTE_TEXT" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/wa_note.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/notes" ``` -------------------------------- ### Verify WhatsApp sender ownership Source: https://context7.com/initechsoftware/openclaw-whatsapp-skills/llms.txt Uses curl and jq to fetch the chat's owning JID and compares it against a predefined allowed JID to prevent persona leakage. ```bash CHAT_ID=12345678 ALLOWED_SENDER_JID="15550100@s.whatsapp.net" # This skill's allowed JID # Get the chat's owning JID CHAT_JID=$(curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID" \ | jq -r '.data.whatsapp_account_id') # Verify ownership before sending if [ "$CHAT_JID" != "$ALLOWED_SENDER_JID" ]; then echo "ERROR: Chat $CHAT_ID owned by $CHAT_JID, not $ALLOWED_SENDER_JID" echo "Refusing to send — would leak persona" exit 0 fi # Safe to send - ownership verified python3 -c "import json; json.dump({'text': 'Hello from the correct persona!'}, open('/tmp/msg.json','w'), ensure_ascii=False)" curl -sS -X POST -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \ -H "Content-Type: application/json" --data-binary @/tmp/msg.json \ "https://app.timelines.ai/integrations/api/chats/$CHAT_ID/messages" ``` -------------------------------- ### Transcribe Voice Notes and Reply Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/guide/capabilities.md This skill transcribes incoming voice notes and replies in either text or voice format. It downloads the voice file from the webhook, runs transcription, and sends replies using POST /chats/{chat_id}/messages for text or POST /chats/{chat_id}/voice_message for voice. ```python POST /chats/{chat_id}/messages {...} ``` ```python POST /chats/{chat_id}/voice_message (multipart .ogg or .mp3) ``` -------------------------------- ### Upload File for Sending Source: https://github.com/initechsoftware/openclaw-whatsapp-skills/blob/main/guide/capabilities.md Upload a file to be attached to a WhatsApp message. Files can be uploaded via URL or directly using multipart form data. ```Python POST /files POST /files_upload ```