agentsclimarketplace

Wa deploy

Skill jackvidal/whatsapp-agent-builder/skills/wa-deploy

Stage 4. Push the generated bot to GitHub, deploy on Render.com, register the public webhook URL with Wasender, and verify a real round-trip message works.From its SKILL.md

Install
npx -y skills add jackvidal/whatsapp-agent-builder --skill wa-deploy

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

9.7 KB, as published. Nobody here has run it

wa-deploy — Take the bot live

Goal: get the bot onto a public HTTPS URL so Wasender can deliver webhooks to it. Render.com is the default — free tier is enough for a personal bot, and the deploy is a single CLI call.

Speak Hebrew. Ask before doing anything that costs money or makes the bot reachable.

Step 1 — Frame the deploy

הבוט שלך מוכן. עכשיו נעלה אותו לאוויר.

נשתמש ב-Render.com — שירות שמריץ את הבוט בענן. יש להם תוכנית חינמית שמספיקה לרוב המקרים. אם הבוט הופך עמוס נשדרג ל-$7 לחודש.

מוכן?

Step 2 — Push the bot to GitHub

The bot needs to live in a Git repo for Render to deploy it.

Ask:

יש לך חשבון GitHub?

  1. כן, ואני מחובר ל-gh CLI כאן
  2. כן, אבל לא מחובר
  3. אין לי

For 1: skip ahead. For 2: tell them to run gh auth login and come back. For 3: walk them through creating one at github.com/signup, then gh auth login.

Then:

cd bot
git init
git add .
git commit -m "initial commit: WhatsApp agent generated by WhatsApp Agent Builder"
gh repo create wa-bot-${BOT_NAME_SLUG} --private --source . --remote origin --push

Capture the repo URL from gh output. Tell the user:

יצרתי לך ריפו פרטי ב-GitHub: {repo_url}. הקוד שלך נשמר שם.

Step 3 — Sign up at Render

Ask:

יש לך חשבון Render?

  1. כן
  2. לא — נירשם עכשיו

For 2:

  1. גש ל https://render.com/register
  2. הירשם עם GitHub (הכי קל) או אימייל.
  3. כשסיימת, תגיד לי "מוכן".

Step 4 — Get a Render API key

בשביל לפרוס מכאן, אצטריך מפתח API של Render:

  1. גש ל https://dashboard.render.com/u/settings#api-keys
  2. צור מפתח חדש בשם "claude-code".
  3. הדבק כאן.

Save to project-root .env as RENDER_API_KEY. Don't echo back.

Step 5 — Create the Render service

Use the Render API (POST /v1/services). The bot is a web_service.

Render API schema notes (verified live, 2026-05):

  • envVars is top-level in the request body, not inside serviceDetails.
  • buildCommand and startCommand go inside serviceDetails.envSpecificDetails (NOT top-level, NOT serviceDetails directly). Putting them at the top level returns "buildCommand is required for non-static, non-docker services" even though they're set there.
  • pythonVersion also goes inside envSpecificDetails.
  • Use runtime: "python" (not the older env: "python" — Render accepts both but runtime is the current key).

Use Python to build the JSON body so secrets stay in env vars and never appear in shell command output:

set -a; source "$ENV_FILE"; set +a
export OWNER_ID="..."  # from GET /v1/owners

python <<'PYEOF' > /tmp/render_create.json
import json, os
body = {
    "type": "web_service",
    "name": "wa-bot-" + os.environ["BOT_NAME_SLUG"],
    "ownerId": os.environ["OWNER_ID"],
    "repo": os.environ["GITHUB_REPO_URL"],
    "branch": "main",
    "autoDeploy": "yes",
    "rootDir": "",
    "envVars": [
        {"key": "WASENDER_API_KEY", "value": os.environ["WASENDER_API_KEY"]},
        {"key": "WASENDER_WEBHOOK_SECRET", "value": os.environ["WASENDER_WEBHOOK_SECRET"]},
        {"key": "WASENDER_ACCOUNT_PROTECTION", "value": "true"},
        {"key": "ANTHROPIC_API_KEY", "value": os.environ["ANTHROPIC_API_KEY"]},
        {"key": "LLM_MODEL", "value": "claude-haiku-4-5"},
        {"key": "MAX_HISTORY", "value": "20"},
    ],
    "serviceDetails": {
        "runtime": "python",
        "plan": "free",
        "region": "singapore",  # frankfurt for Israel, oregon for Americas, singapore for Asia
        "envSpecificDetails": {
            "pythonVersion": "3.12.7",
            "buildCommand": "pip install -r requirements.txt",
            "startCommand": "uvicorn main:app --host 0.0.0.0 --port $PORT",
        },
        "healthCheckPath": "/healthz",
    },
}
print(json.dumps(body))
PYEOF

curl -s -X POST "https://api.render.com/v1/services" \
  -H "Authorization: Bearer $RENDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d @/tmp/render_create.json
rm -f /tmp/render_create.json

OWNER_ID comes from GET /v1/owners first:

curl -s "https://api.render.com/v1/owners" \
  -H "Authorization: Bearer $RENDER_API_KEY" | jq -r '.[0].owner.id'

Region: pick frankfurt for Israeli users (lowest latency to most Israeli ISPs), oregon for Americas, singapore for Asia/Pacific.

Save the returned service.id and service.serviceDetails.url to .wa-state.json as render_service_id and render_url.

Step 6 — Wait for the build

Poll GET /v1/services/{id}/deploys?limit=1 until status becomes live. Show progress in Hebrew:

בונה... (~2 דקות) מתקין תלויות... מפעיל... 🎉 הבוט באוויר ב: {render_url}

Verify:

curl -s "$RENDER_URL/healthz"

Expect {"status":"ok"}. If it fails, fetch the latest deploy logs (GET /v1/services/{id}/deploys/{deployId}/events) and surface the error in Hebrew.

Step 7 — Register the webhook URL with Wasender

Now the critical bit: tell Wasender where to deliver inbound messages.

Branch on setup_path from .wa-state.json:

Path A (PAT available) — programmatic

curl -sX PUT "https://www.wasenderapi.com/api/whatsapp-sessions/$WASENDER_SESSION_ID" \
  -H "Authorization: Bearer $WASENDER_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "'$RENDER_URL'/webhook/wasender",
    "webhook_enabled": true,
    "webhook_events": ["messages.received", "session.status"]
  }'

Verify:

curl -sX GET "https://www.wasenderapi.com/api/whatsapp-sessions/$WASENDER_SESSION_ID" \
  -H "Authorization: Bearer $WASENDER_PAT" | jq '.data.webhook_url'

Should return the Render URL.

Path B (no PAT, dashboard-managed session) — manual

The user has to set the webhook URL via the dashboard. Tell them (in Hebrew):

עכשיו רק תעדכן את ה-Webhook ב-Wasender ידנית:

  1. גש ל-https://wasenderapi.com/dashboard ופתח את הסשן שלך.
  2. לחץ על Manage Webhook (כפתור ימני עליון).
  3. הדבק את הכתובת הזו בשדה Webhook URL: {RENDER_URL}/webhook/wasender
  4. סמן את האירוע messages.received (וגם session.status אם רוצים התראה על ניתוקים).
  5. חשוב: וודא שהשדה Webhook Secret זהה למה שיש ב-.env שלך (WASENDER_WEBHOOK_SECRET). אם לא, או החלף את אחד מהם, או צור חדש בשני המקומות.
  6. לחץ Save.

תגיד לי "שמרתי" ואבדוק שהכל עובד.

Wait for confirmation. Then proceed to step 8.

Step 8 — Real round-trip test

Tell the user:

הכל מוכן. שלח עכשיו הודעה אמיתית מהטלפון שלך אל {WASENDER_PHONE}. אני אבדוק שהבוט קיבל וענה.

Wait 30 seconds. Then check Render logs:

curl -s "https://api.render.com/v1/services/$RENDER_SERVICE_ID/logs?limit=20" \
  -H "Authorization: Bearer $RENDER_API_KEY"

Look for the inbound webhook line. If found and the user confirms they received a reply on their phone:

🚀 הבוט שלך חי! מספר {WASENDER_PHONE} עונה עכשיו אוטומטית.

If no inbound seen after 60 seconds, run the diagnostic from wa-maintain (don't make the user wait — proactively diagnose).

Step 9 — Update state

Update .wa-state.json:

{
  "current_stage": "connect",
  "completed_stages": ["setup", "characterize", "build", "deploy"],
  "render_service_id": "<id>",
  "render_url": "<url>",
  "last_updated": "<ISO>"
}

Tell the user:

השלבים הבאים אופציונליים — להוסיף לבוט יכולות נוספות (יומן, מייל, תזכורות מתמשכות) או לבחור איפה ההיסטוריה נשמרת. רוצה להמשיך, או לעצור כאן?

If they want more: route to wa-connect. If stop: route to wa-maintain (idle mode).

Common pitfalls

  • Render free tier sleeps after 15 min idle. First message after sleep takes ~30s to wake. Mention this to the user. Suggest the $7 paid tier if always-on matters.
  • 404 immediately after a fresh deploy. Even on first deploy, Render's edge can return 404 Not Found (10 bytes, plain text) for ~10–60s while the service is still spinning up internally. The deploy-status API may already say live while curl gets 404s. Don't panic and don't redeploy — wait 30s and retry /healthz. If it's still 404 after 90s, then something is genuinely wrong (check GET /v1/services/{id} for suspended, and check deploy events).
  • Build fails on Python version. If Render reports "no matching Python", check .python-version and/or set PYTHON_VERSION=3.12.7 env var on the service.
  • Webhook signature mismatch. Triple-check WASENDER_WEBHOOK_SECRET env var on Render matches the one Wasender returned at session-create time. If user regenerated the secret in dashboard, update both Render env and bot/.env locally.
  • Wasender returns 422 on PUT session. Some fields may be read-only. If updating webhook fails, try POST /api/whatsapp-sessions/{id}/webhook (some accounts have a dedicated webhook endpoint — verify in their dashboard).
  • Account protection rate limits. With account_protection: true, the bot can only send 1 message per 5 seconds. If a long reply gets split into multiple sends (or there's a burst), they'll queue. Mention this if user complains "the bot sometimes feels slow."

Keep looking

Skills are one crate of 628,853. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.