API REFERENCE
AUTHENTICATION
All requests require your API key as a Bearer token. Store it as an environment variable. Never hardcode it in scripts.
export WA_API_KEY="sk_..."
Authorization: Bearer $WA_API_KEY
Sign in to get your API key.
RATE LIMITS
All limits are per API key. Exceeded requests return 429 Too Many Requests with a Retry-After header indicating seconds until the window resets.
| Endpoint | Limit | Window |
|---|---|---|
POST /api/instances (provision) | 10 req | per minute |
/api/instances/:id/proxy/* | 120 req | per minute |
/api/* (all other endpoints) | 120 req | per minute |
| All endpoints (per IP, global floor) | 300 req | per minute |
HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json
{"error":"rate limit exceeded"}
Implement exponential backoff. Check Retry-After before retrying.
IDEMPOTENCY
Message-producing endpoints (the /send/* family plus message edit and revoke) accept an optional Idempotency-Key header that makes them safe to retry: if your request times out, retry it with the same key — the message is guaranteed to be sent at most once.
curl -X POST $BASE/send/text \
-H "Authorization: Bearer $WA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-8812-confirmation" \
-d '{"chat":"15550001234","body":"Your order shipped!"}'
| First use of a key | Message is sent; the response is stored for 24 hours |
| Retry with the same key | Stored response replayed with X-Idempotency-Replayed: true; nothing is re-sent |
| Concurrent duplicate | 409 with code idempotency_conflict while the first request is in flight |
| Failed send | Does not consume the key — retry with the same key sends normally |
Use a unique value per logical message (a UUID or your own order/job id, max 255 chars). Without the header, requests behave exactly as before — the feature is fully opt-in.
BRIDGES
Manage bridge instances. All routes require Authorization: Bearer $WA_API_KEY.
https://wabridges.com/api/instances
Provision a new bridge. If customer_ref already exists, returns the existing record.
curl -X POST https://wabridges.com/api/instances \
-H "Authorization: Bearer $WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_ref": "user-123",
"webhook_url": "https://yourbackend.com/hook"
}'
{
"id": "7d09aa9c-2f4e-4b6a-9c1d-3e5f7a9b1c2d",
"customer_ref": "user-123",
"state": "running",
"created_at": 1777180605
}
Errors: 402 no active subscription or payment past due · 403 account bridge limit reached (default 10 — contact support to raise)
https://wabridges.com/api/instances
List all bridges for this account.
curl https://wabridges.com/api/instances \ -H "Authorization: Bearer $WA_API_KEY"
[
{
"id": "7d09aa9c-2f4e-4b6a-9c1d-3e5f7a9b1c2d",
"customer_ref": "user-123",
"state": "running",
"created_at": 1777180605
}
]
https://wabridges.com/api/instances/:customer_ref
Get a single bridge including live container state from the manager.
curl https://wabridges.com/api/instances/{customer_ref} \
-H "Authorization: Bearer $WA_API_KEY"
{
"id": "7d09aa9c-2f4e-4b6a-9c1d-3e5f7a9b1c2d",
"customer_ref": "user-123",
"state": "running",
"created_at": 1777180605,
"webhook_url": "https://yourbackend.com/hook",
"webhook_secret": "whsec_...",
"on_trial": false,
"manager": {
"health": "healthy",
"started_at": "2025-06-17T10:00:00Z",
"fail_count": 0
}
}
webhook_secret is sent as Authorization: Bearer <webhook_secret> on every outbound webhook — verify it before trusting payloads.
https://wabridges.com/api/instances/:customer_ref/logs
Stream bridge logs as server-sent events (SSE). Optional ?tail=N sets how many trailing lines to start from (default 100, max 10000).
curl -N https://wabridges.com/api/instances/{customer_ref}/logs?tail=50 \
-H "Authorization: Bearer $WA_API_KEY"
data: 2025-06-17T10:00:01Z INF websocket connected data: 2025-06-17T10:00:02Z INF webhook delivered event=message status=200
SESSION
https://wabridges.com/api/instances/{customer_ref}/proxy/status
Connection status
{"jid":"","name":"","phone":"","status":"unpaired"}
https://wabridges.com/api/instances/{customer_ref}/proxy/qr
QR code PNG for pairing
(image/png)
https://wabridges.com/api/instances/{customer_ref}/proxy/pair
Pair via link code sent to phone
{"phone":"15550001234"}
{"code":"ABCD-EFGH"}
https://wabridges.com/api/instances/{customer_ref}/proxy/logout
Log out of WhatsApp session
{"ok":true}
https://wabridges.com/api/instances/{customer_ref}/proxy/webhook-logs
Returns up to 200 most recent outbound webhook delivery attempts, newest first. Ring buffer — oldest entries are evicted once full.
[{"attempt":0,"error":"","event":"","event_id":"","latency_ms":0,"status_code":0,"success":false,"timestamp":""}]
MESSAGING
https://wabridges.com/api/instances/{customer_ref}/proxy/send/text
Send a text message
{"body":"hello","chat":"15550001234@s.whatsapp.net"}
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
https://wabridges.com/api/instances/{customer_ref}/proxy/send/media
Media type is inferred from the Content-Type of the URL response.
{"chat":"15550001234","url":"https://example.com/voice.ogg"}
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
https://wabridges.com/api/instances/{customer_ref}/proxy/send/reaction
React to a message with an emoji
{"chat":"15550001234","emoji":"👍","message_id":"ACE41E988162AB19A4A7BB7A9E663693"}
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
https://wabridges.com/api/instances/{customer_ref}/proxy/send/poll
Send a poll
{"chat":"15550001234","max_answers":3,"options":["Mon","Tue","Wed","Thu","Fri"],"question":"Which days work for you?"}
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
https://wabridges.com/api/instances/{customer_ref}/proxy/send/location
Send a location message
{"address":"Plaza de la Constitución, Mexico City","chat":"15550001234","lat":19.432608,"lng":-99.133209,"name":"Zócalo"}
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
https://wabridges.com/api/instances/{customer_ref}/proxy/send/event
Sends a WA EventMessage. The bridge embeds a fresh MessageSecret in MessageContextInfo so future EncEventResponseMessage RSVPs can be decrypted and delivered as event_response webhooks.
{"chat":"120363000000000001@g.us","description":"weekly status","end_time":1777338000,"has_reminder":true,"is_schedule_call":true,"join_link":"https://meet.example.com/team-sync","name":"Team sync","reminder_offset_sec":900,"start_time":1777334400}
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
https://wabridges.com/api/instances/{customer_ref}/proxy/send/typing
Send a typing indicator
{"chat":"15550001234","state":"composing"}
{"ok":true}
https://wabridges.com/api/instances/{customer_ref}/proxy/messages/:id
Revoke a sent message for everyone
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
https://wabridges.com/api/instances/{customer_ref}/proxy/messages/:id/edit
Edit a sent text message
{"body":"corrected text","chat":"15550001234@s.whatsapp.net"}
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
https://wabridges.com/api/instances/{customer_ref}/proxy/messages/:id/read
Send a read receipt
{"chat":"15550001234@s.whatsapp.net"}
{"ok":true}
https://wabridges.com/api/instances/{customer_ref}/proxy/messages/:id/media
Download received media by message_id (cached 3h)
(image/jpeg)
CONTACTS
https://wabridges.com/api/instances/{customer_ref}/proxy/contacts
Returns a plain JSON array of contacts sorted by JID ascending. Without parameters the full list is returned (back-compat). To page, pass `limit` and use the last JID of the previous page as `after` on the next request; a page shorter than `limit` means you have reached the end.
[{"about":"","business_address":"","business_email":"","business_name":"","first_name":"","full_name":"","is_business":false,"jid":"","name":"","phone":"","redacted_phone":"","registered":false,"verified_name":""}]
https://wabridges.com/api/instances/{customer_ref}/proxy/contacts/check
Check which phone numbers are on WhatsApp
{"phones":["15550001234","00000000000"]}
[{"is_business":false,"is_on_whatsapp":false,"jid":"","phone":"","registered":false,"verified_name":""}]
https://wabridges.com/api/instances/{customer_ref}/proxy/contacts/:jid
Get info for a single contact
{"about":"","business_address":"","business_email":"","business_name":"","first_name":"","full_name":"","is_business":false,"jid":"","name":"","phone":"","redacted_phone":"","registered":false,"verified_name":""}
https://wabridges.com/api/instances/{customer_ref}/proxy/contacts/:jid/avatar
Download a contact's avatar
(image/jpeg)
PROFILE
https://wabridges.com/api/instances/{customer_ref}/proxy/profile
Your own profile
{"about":"","jid":"","name":"","phone":""}
https://wabridges.com/api/instances/{customer_ref}/proxy/profile/status
Set your About status text
{"status":"Hey there!"}
{"ok":true}
https://wabridges.com/api/instances/{customer_ref}/proxy/profile/avatar
Download your own avatar
(image/jpeg)
https://wabridges.com/api/instances/{customer_ref}/proxy/profile/avatar
Server fetches the URL (must return JPEG) and uploads it as the account profile picture.
{"url":"https://example.com/me.jpg"}
{"picture_id":""}
https://wabridges.com/api/instances/{customer_ref}/proxy/profile/avatar
Remove your profile picture
{"ok":true}
https://wabridges.com/api/instances/{customer_ref}/proxy/profile/qr
Personal QR link to share your contact
{"link":"https://wa.me/qr/ABCDEFGHIJKL"}
PRIVACY
https://wabridges.com/api/instances/{customer_ref}/proxy/status/privacy
Get who can see your status updates
[{"is_default":false,"list":[""],"type":"contacts"}]
PRESENCE
https://wabridges.com/api/instances/{customer_ref}/proxy/presence
Set your online/offline presence
{"state":"unavailable"}
{"ok":true}
https://wabridges.com/api/instances/{customer_ref}/proxy/presence/subscribe
WhatsApp only pushes ChatPresence (typing/recording) and Presence (online/offline) for contacts the client has subscribed to. Subscriptions decay after a few minutes; refresh periodically. Requires global presence to be `available` first.
{"chat":"15550001234"}
{"ok":true}
DEBUG
https://wabridges.com/api/instances/{customer_ref}/proxy/healthz
Used by the container HEALTHCHECK. Always returns 200 while the process is up. Does NOT check WhatsApp connection — use /status for that.
{"ok":true}
https://wabridges.com/api/instances/{customer_ref}/proxy/debug/stats
Runtime stats (goroutines, heap, cache)
{"gc_cycles":0,"goroutines":0,"heap_alloc_mb":0,"heap_objects":0,"heap_sys_mb":0,"media_cache_size":0,"next_gc_mb":0,"total_alloc_mb":0}
WEBHOOKS
Every inbound event fires a POST to your webhook_url.
Single-attempt, no retries. Respond 200 immediately — process async. 10s timeout.
See the Webhook Guide for delivery behavior, handler examples, and all event types.
message
Fields: event (message), message_id, contact_id, phone, chat_id, name, body, type (text|media|reaction|poll|location|event), media_type (|image|video|audio|voice|document|sticker), is_group, from_me, timestamp, caption?, edit (edit|revoke|pin)?, event_details?, forwarded?, is_broadcast?, max_answers?, mentions?, offline?, options?, quoted?, reaction?, verified_business?
{"body":"Team sync","chat_id":"15550009999@g.us","contact_id":"15550001234@s.whatsapp.net","event":"message","event_details":{"description":"weekly status","end_time":1777338000,"join_link":"https://meet.example.com/team-sync","name":"Team sync","start_time":1777334400},"from_me":false,"is_group":true,"media_type":"","message_id":"ACEVENT0001","name":"John Doe","phone":"15550001234","timestamp":1777330600,"type":"event"}
connected
Fields: event (connected), phone
{"event":"connected","phone":"15550001234"}
disconnected
Fields: event (disconnected), detail?, reason (logged_out|stream_replaced)?
{"detail":"401: logged out","event":"disconnected","reason":"logged_out"}
typing
Fields: event (typing), contact_id, chat_id, state (composing|paused), mode (text|voice)
{"chat_id":"15550001234@s.whatsapp.net","contact_id":"15550001234@s.whatsapp.net","event":"typing","mode":"text","state":"composing"}
poll_vote
Fields: event (poll_vote), contact_id, phone, chat_id, poll_id, message_id, name, from_me, timestamp, selected_options, offline?
{"chat_id":"15550001234@s.whatsapp.net","contact_id":"15550001234@s.whatsapp.net","event":"poll_vote","from_me":false,"message_id":"ACVOTE0099","name":"John Doe","offline":true,"phone":"15550001234","poll_id":"ACPOLL0001","selected_options":["red"],"timestamp":1777180800}
event_response
Fields: event (event_response), contact_id, phone, chat_id, event_id, message_id, name, from_me, timestamp, response (going|not_going|maybe|unknown), response_time_ms, extra_guest_count, offline?
{"chat_id":"15550009999@g.us","contact_id":"15550001234@s.whatsapp.net","event":"event_response","event_id":"ACEVENT0001","extra_guest_count":1,"from_me":false,"message_id":"ACEVRESP0001","name":"John Doe","phone":"15550001234","response":"going","response_time_ms":1777330700123,"timestamp":1777330700}
call_incoming
Fields: event (call_incoming), call_id, contact_id, platform, timestamp
{"call_id":"ABCDEF123456","contact_id":"15550001234@s.whatsapp.net","event":"call_incoming","platform":"android","timestamp":1777180605}
call_terminated
Fields: event (call_terminated), call_id, contact_id, reason (timeout|hangup|decline|busy|accept_elsewhere), timestamp
{"call_id":"ABCDEF123456","contact_id":"15550001234@s.whatsapp.net","event":"call_terminated","reason":"hangup","timestamp":1777180720}
offline_sync_preview
Fields: event (offline_sync_preview), total, messages, notifications, receipts, app_data_changes
{"app_data_changes":0,"event":"offline_sync_preview","messages":120,"notifications":8,"receipts":14,"total":142}
offline_sync_completed
Fields: event (offline_sync_completed), count
{"count":142,"event":"offline_sync_completed"}
profile_picture_updated
Fields: event (profile_picture_updated), remove, timestamp, picture_id?
{"event":"profile_picture_updated","remove":true,"timestamp":1777180800}