Development
penwa - Claude MCP Skill
Drive the OpenWA WhatsApp HTTP API from the terminal with curl. Use when sending WhatsApp messages (text/image/video/audio/document/location/contact/bulk), managing sessions and QR login, listing contacts/groups/chats, registering webhooks, or managing scoped API keys against an OpenWA server. Triggers on "OpenWA", "penwa", "send WhatsApp via API", "WhatsApp session", or mentions of base URL http://0.0.0.0:2785.
SEO Guide: Enhance your AI agent with the penwa tool. This Model Context Protocol (MCP) server allows Claude Desktop and other LLMs to drive the openwa whatsapp http api from the terminal with curl. use when sending whatsapp messages (... Download and configure this skill to unlock new capabilities for your AI workflow.
Documentation
SKILL.md# penwa — OpenWA API via curl
Talk to an [OpenWA](https://github.com/rmyndharis/OpenWA) WhatsApp server over its REST API using `curl`. Spec: `docs/06-api-specification.md`.
## Connection
| Setting | Flag | Env var | Default |
|----------|--------------|-------------------|-----------------------|
| Base URL | `--base-url` | `OPENWA_BASE_URL` | `http://0.0.0.0:2785` |
| API key | `--api-key` | `OPENWA_API_KEY` | `dev-admin-key` |
- **All API paths are prefixed with `/api`** → e.g. `http://0.0.0.0:2785/api/sessions`.
- **Auth header is `X-API-Key`** (not `Authorization`). Send it on every call except `GET /health`.
- Set up the shell once, then every snippet below works as-is:
```bash
export OPENWA_BASE_URL="${OPENWA_BASE_URL:-http://0.0.0.0:2785}"
export OPENWA_API_KEY="${OPENWA_API_KEY:-dev-admin-key}"
BASE="$OPENWA_BASE_URL/api"
AUTH=(-H "X-API-Key: $OPENWA_API_KEY" -H "Content-Type: application/json")
```
> `chatId` format: individuals `<countrycode><number>@c.us` (e.g. `628123456789@c.us`); groups `<id>@g.us`. No `+`, spaces, or dashes.
## Quick start: verify, log in, send
```bash
# 1. Is the server up? (public, no key)
curl -s "$BASE/health"
# 2. Is my key valid?
curl -s -X POST "$BASE/auth/validate" "${AUTH[@]}"
# 3. Create a session
curl -s -X POST "$BASE/sessions" "${AUTH[@]}" -d '{"name":"main"}'
# -> note the returned sessionId, e.g. sess_abc123
# 4. Get the QR (scan in WhatsApp > Linked devices). Returns an image.
SID=sess_abc123
curl -s "$BASE/sessions/$SID/qr" -H "X-API-Key: $OPENWA_API_KEY" -o qr.png && open qr.png
# 5. Poll until connected
curl -s "$BASE/sessions/$SID" "${AUTH[@]}" # watch for status: connected
# 6. Send your first message
curl -s -X POST "$BASE/sessions/$SID/messages/send-text" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","text":"Hello from OpenWA!"}'
```
Add `-H "X-Request-ID: req_$(date +%s)"` to any write call for traceable logs. Pipe responses through `| jq` to read them.
## Sessions
```bash
# List (optional ?status= ?page= ?limit=)
curl -s "$BASE/sessions?status=connected&limit=20" "${AUTH[@]}"
# Create with a webhook attached
curl -s -X POST "$BASE/sessions" "${AUTH[@]}" \
-d '{"name":"sales-bot","webhook":"https://example.com/hook"}'
# Details / QR / logout / delete
curl -s "$BASE/sessions/$SID" "${AUTH[@]}"
curl -s "$BASE/sessions/$SID/qr" -H "X-API-Key: $OPENWA_API_KEY" -o qr.png
curl -s -X POST "$BASE/sessions/$SID/logout" "${AUTH[@]}"
curl -s -X DELETE "$BASE/sessions/$SID" "${AUTH[@]}"
```
## Sending messages
All send endpoints are `POST /sessions/:sessionId/messages/send-<type>`. Media accepts either `{"url":"..."}` or a base64 `{"data":"...","mimetype":"..."}` object — `{"url":...}` shown.
```bash
SEND="$BASE/sessions/$SID/messages"
# Text
curl -s -X POST "$SEND/send-text" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","text":"Hello *world*"}'
# Image (with caption)
curl -s -X POST "$SEND/send-image" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","image":{"url":"https://example.com/pic.jpg"},"caption":"Check this out!"}'
# Video
curl -s -X POST "$SEND/send-video" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","video":{"url":"https://example.com/clip.mp4"},"caption":"demo"}'
# Audio / voice note (ptt:true = push-to-talk bubble)
curl -s -X POST "$SEND/send-audio" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","audio":{"url":"https://example.com/vn.ogg"},"ptt":true}'
# Document
curl -s -X POST "$SEND/send-document" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","document":{"url":"https://example.com/invoice.pdf"},"filename":"invoice.pdf","caption":"Your invoice"}'
# Location
curl -s -X POST "$SEND/send-location" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","latitude":-6.2088,"longitude":106.8456,"description":"Jakarta HQ"}'
# Contact card
curl -s -X POST "$SEND/send-contact" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","contact":{"name":"Jane","phone":"628987654321"}}'
```
### Bulk send
```bash
# Queue a batch
curl -s -X POST "$SEND/send-bulk" "${AUTH[@]}" -d '{
"messages":[
{"chatId":"628111111111@c.us","text":"Hi A"},
{"chatId":"628222222222@c.us","text":"Hi B"}
]
}'
# -> returns a batchId
BID=batch_xyz
curl -s "$SEND/batch/$BID" "${AUTH[@]}" # status/progress
curl -s -X POST "$SEND/batch/$BID/cancel" "${AUTH[@]}" # cancel
```
### Message history
```bash
curl -s "$BASE/sessions/$SID/chats/628123456789@c.us/messages?limit=50" "${AUTH[@]}"
# ?before=<messageId> to paginate older
```
## Contacts
```bash
curl -s "$BASE/sessions/$SID/contacts" "${AUTH[@]}"
# Does this number have WhatsApp?
curl -s "$BASE/sessions/$SID/contacts/check/628123456789" "${AUTH[@]}"
# Avatar
curl -s "$BASE/sessions/$SID/contacts/628123456789@c.us/profile-picture" "${AUTH[@]}"
```
## Groups
```bash
curl -s "$BASE/sessions/$SID/groups" "${AUTH[@]}" # list
curl -s "$BASE/sessions/$SID/groups/120363000000000000@g.us" "${AUTH[@]}" # info + participants
# Create
curl -s -X POST "$BASE/sessions/$SID/groups" "${AUTH[@]}" \
-d '{"name":"Project Team","participants":["628111111111@c.us","628222222222@c.us"]}'
```
## Webhooks
```bash
# Register
curl -s -X POST "$BASE/sessions/$SID/webhooks" "${AUTH[@]}" -d '{
"url":"https://example.com/wa-hook",
"events":["message.received","message.ack","session.status"],
"secret":"shhh"
}'
curl -s "$BASE/sessions/$SID/webhooks" "${AUTH[@]}" # list
curl -s -X DELETE "$BASE/sessions/$SID/webhooks/wh_123" "${AUTH[@]}" # delete
```
## API key management (admin role)
```bash
# Create a scoped key
curl -s -X POST "$BASE/auth/api-keys" "${AUTH[@]}" \
-d '{"name":"n8n Integration","role":"operator"}'
# Optional fields: allowedIps[], allowedSessions[], expiresAt (ISO)
curl -s "$BASE/auth/api-keys" "${AUTH[@]}" # list
curl -s "$BASE/auth/api-keys/key_123" "${AUTH[@]}" # details
curl -s -X PUT "$BASE/auth/api-keys/key_123" "${AUTH[@]}" -d '{"role":"viewer"}'
curl -s -X POST "$BASE/auth/api-keys/key_123/revoke" "${AUTH[@]}"
curl -s -X DELETE "$BASE/auth/api-keys/key_123" "${AUTH[@]}"
```
## Health
```bash
curl -s "$BASE/health" # public, no key
curl -s "$BASE/health/detailed" "${AUTH[@]}" # auth required
```
## Tips & gotchas
- **`/api` prefix** — endpoints in the spec tables omit it; always prepend it (already baked into `$BASE`).
- **QR is binary** — write to a file with `-o qr.png`; don't dump it to the terminal.
- **`X-API-Key`, not Bearer** — a `401` almost always means wrong header name or key.
- **chatId hygiene** — strip `+`/spaces; suffix `@c.us` (person) or `@g.us` (group). Use `contacts/check` before sending to unknown numbers.
- **Debug a call** — add `-i` to see status + headers, or `-w '\n%{http_code}\n'` for just the code.
- **WebSocket** for realtime: `wss://<host>/ws?apiKey=$OPENWA_API_KEY`, then send `{"type":"subscribe","payload":{"sessionId":"...","events":[...]}}`.
- Reach for `jq` to extract fields, e.g. `... | jq -r '.sessionId'` to capture and reuse.Signals
Information
- Repository
- ronnycoding/.claude
- Author
- ronnycoding
- Last Sync
- 9/6/2026
- Repo Updated
- 6/26/2026
- Created
- 6/17/2026
Reviews (0)
No reviews yet. Be the first to review this skill!
Related Skills
cursorrules
CrewAI Development Rules
README
Agents — Working Implementations
cn-check
Install and run the Continue CLI (`cn`) to execute AI agent checks on local code changes. Use when asked to "run checks", "lint with AI", "review my changes with cn", or set up Continue CI locally.
firecrawl-build-search
Integrate Firecrawl `/search` into product code and agent workflows. Use when an app needs discovery before extraction, when the feature starts with a query instead of a URL, or when the system should search the web and optionally hydrate result content.
Related Guides
Bear Notes Claude Skill: Your AI-Powered Note-Taking Assistant
Learn how to use the bear-notes Claude skill. Complete guide with installation instructions and examples.
Mastering tmux with Claude: A Complete Guide to the tmux Claude Skill
Learn how to use the tmux Claude skill. Complete guide with installation instructions and examples.
OpenAI Whisper API Claude Skill: Complete Guide to AI-Powered Audio Transcription
Learn how to use the openai-whisper-api Claude skill. Complete guide with installation instructions and examples.