| name | penwa |
| description | 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. |
penwa — OpenWA API via curl
Talk to an 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:
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
curl -s "$BASE/health"
curl -s -X POST "$BASE/auth/validate" "${AUTH[@]}"
curl -s -X POST "$BASE/sessions" "${AUTH[@]}" -d '{"name":"main"}'
SID=sess_abc123
curl -s "$BASE/sessions/$SID/qr" -H "X-API-Key: $OPENWA_API_KEY" -o qr.png && open qr.png
curl -s "$BASE/sessions/$SID" "${AUTH[@]}"
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
curl -s "$BASE/sessions?status=connected&limit=20" "${AUTH[@]}"
curl -s -X POST "$BASE/sessions" "${AUTH[@]}" \
-d '{"name":"sales-bot","webhook":"https://example.com/hook"}'
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.
SEND="$BASE/sessions/$SID/messages"
curl -s -X POST "$SEND/send-text" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","text":"Hello *world*"}'
curl -s -X POST "$SEND/send-image" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","image":{"url":"https://example.com/pic.jpg"},"caption":"Check this out!"}'
curl -s -X POST "$SEND/send-video" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","video":{"url":"https://example.com/clip.mp4"},"caption":"demo"}'
curl -s -X POST "$SEND/send-audio" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","audio":{"url":"https://example.com/vn.ogg"},"ptt":true}'
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"}'
curl -s -X POST "$SEND/send-location" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","latitude":-6.2088,"longitude":106.8456,"description":"Jakarta HQ"}'
curl -s -X POST "$SEND/send-contact" "${AUTH[@]}" \
-d '{"chatId":"628123456789@c.us","contact":{"name":"Jane","phone":"628987654321"}}'
Bulk send
curl -s -X POST "$SEND/send-bulk" "${AUTH[@]}" -d '{
"messages":[
{"chatId":"628111111111@c.us","text":"Hi A"},
{"chatId":"628222222222@c.us","text":"Hi B"}
]
}'
BID=batch_xyz
curl -s "$SEND/batch/$BID" "${AUTH[@]}"
curl -s -X POST "$SEND/batch/$BID/cancel" "${AUTH[@]}"
Message history
curl -s "$BASE/sessions/$SID/chats/628123456789@c.us/messages?limit=50" "${AUTH[@]}"
Contacts
curl -s "$BASE/sessions/$SID/contacts" "${AUTH[@]}"
curl -s "$BASE/sessions/$SID/contacts/check/628123456789" "${AUTH[@]}"
curl -s "$BASE/sessions/$SID/contacts/628123456789@c.us/profile-picture" "${AUTH[@]}"
Groups
curl -s "$BASE/sessions/$SID/groups" "${AUTH[@]}"
curl -s "$BASE/sessions/$SID/groups/120363000000000000@g.us" "${AUTH[@]}"
curl -s -X POST "$BASE/sessions/$SID/groups" "${AUTH[@]}" \
-d '{"name":"Project Team","participants":["628111111111@c.us","628222222222@c.us"]}'
Webhooks
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[@]}"
curl -s -X DELETE "$BASE/sessions/$SID/webhooks/wh_123" "${AUTH[@]}"
API key management (admin role)
curl -s -X POST "$BASE/auth/api-keys" "${AUTH[@]}" \
-d '{"name":"n8n Integration","role":"operator"}'
curl -s "$BASE/auth/api-keys" "${AUTH[@]}"
curl -s "$BASE/auth/api-keys/key_123" "${AUTH[@]}"
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
curl -s "$BASE/health"
curl -s "$BASE/health/detailed" "${AUTH[@]}"
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.