WABridges
← Docs API REFERENCE
Docs / API Reference

API REFERENCE

v1 Updated July 2026

AUTHENTICATION

All requests require your API key as a Bearer token. Store it as an environment variable. Never hardcode it in scripts.

SHELL
export WA_API_KEY="sk_..."
HTTP HEADER
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.

EndpointLimitWindow
POST /api/instances (provision)10 reqper minute
/api/instances/:id/proxy/*120 reqper minute
/api/* (all other endpoints)120 reqper minute
All endpoints (per IP, global floor)300 reqper minute
429 RESPONSE
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.

SEND WITH IDEMPOTENCY KEY
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 keyMessage is sent; the response is stored for 24 hours
Retry with the same keyStored response replayed with X-Idempotency-Replayed: true; nothing is re-sent
Concurrent duplicate409 with code idempotency_conflict while the first request is in flight
Failed sendDoes 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.

POST https://wabridges.com/api/instances

Provision a new bridge. If customer_ref already exists, returns the existing record.

customer_refstring, requiredYour internal identifier for this bridge (e.g. user ID). Alphanumeric + dashes.
webhook_urlstring, requiredURL that receives all inbound WhatsApp events as POST requests.
REQUEST
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"
  }'
RESPONSE 200
{
  "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)

GET https://wabridges.com/api/instances

List all bridges for this account.

REQUEST
curl https://wabridges.com/api/instances \
  -H "Authorization: Bearer $WA_API_KEY"
RESPONSE 200
[
  {
    "id": "7d09aa9c-2f4e-4b6a-9c1d-3e5f7a9b1c2d",
    "customer_ref": "user-123",
    "state": "running",
    "created_at": 1777180605
  }
]
GET https://wabridges.com/api/instances/:customer_ref

Get a single bridge including live container state from the manager.

REQUEST
curl https://wabridges.com/api/instances/{customer_ref} \
  -H "Authorization: Bearer $WA_API_KEY"
RESPONSE 200
{
  "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.

GET 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).

REQUEST
curl -N https://wabridges.com/api/instances/{customer_ref}/logs?tail=50 \
  -H "Authorization: Bearer $WA_API_KEY"
RESPONSE 200 (text/event-stream)
data: 2025-06-17T10:00:01Z INF websocket connected
data: 2025-06-17T10:00:02Z INF webhook delivered event=message status=200

SESSION

GET https://wabridges.com/api/instances/{customer_ref}/proxy/status

Connection status

RESPONSE
{"jid":"","name":"","phone":"","status":"unpaired"}
GET https://wabridges.com/api/instances/{customer_ref}/proxy/qr

QR code PNG for pairing

RESPONSE
(image/png)
POST https://wabridges.com/api/instances/{customer_ref}/proxy/pair

Pair via link code sent to phone

REQUEST BODY
{"phone":"15550001234"}
RESPONSE
{"code":"ABCD-EFGH"}
DELETE https://wabridges.com/api/instances/{customer_ref}/proxy/logout

Log out of WhatsApp session

RESPONSE
{"ok":true}
GET 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.

RESPONSE
[{"attempt":0,"error":"","event":"","event_id":"","latency_ms":0,"status_code":0,"success":false,"timestamp":""}]

MESSAGING

POST https://wabridges.com/api/instances/{customer_ref}/proxy/send/text

Send a text message

REQUEST BODY
{"body":"hello","chat":"15550001234@s.whatsapp.net"}
RESPONSE
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
POST https://wabridges.com/api/instances/{customer_ref}/proxy/send/media

Media type is inferred from the Content-Type of the URL response.

REQUEST BODY
{"chat":"15550001234","url":"https://example.com/voice.ogg"}
RESPONSE
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
POST https://wabridges.com/api/instances/{customer_ref}/proxy/send/reaction

React to a message with an emoji

REQUEST BODY
{"chat":"15550001234","emoji":"👍","message_id":"ACE41E988162AB19A4A7BB7A9E663693"}
RESPONSE
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
POST https://wabridges.com/api/instances/{customer_ref}/proxy/send/poll

Send a poll

REQUEST BODY
{"chat":"15550001234","max_answers":3,"options":["Mon","Tue","Wed","Thu","Fri"],"question":"Which days work for you?"}
RESPONSE
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
POST https://wabridges.com/api/instances/{customer_ref}/proxy/send/location

Send a location message

REQUEST BODY
{"address":"Plaza de la Constitución, Mexico City","chat":"15550001234","lat":19.432608,"lng":-99.133209,"name":"Zócalo"}
RESPONSE
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
POST 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.

REQUEST BODY
{"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}
RESPONSE
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
POST https://wabridges.com/api/instances/{customer_ref}/proxy/send/typing

Send a typing indicator

REQUEST BODY
{"chat":"15550001234","state":"composing"}
RESPONSE
{"ok":true}
DELETE https://wabridges.com/api/instances/{customer_ref}/proxy/messages/:id

Revoke a sent message for everyone

RESPONSE
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
PATCH https://wabridges.com/api/instances/{customer_ref}/proxy/messages/:id/edit

Edit a sent text message

REQUEST BODY
{"body":"corrected text","chat":"15550001234@s.whatsapp.net"}
RESPONSE
{"message_id":"ACE41E988162AB19A4A7BB7A9E663693","timestamp":1777180605}
POST https://wabridges.com/api/instances/{customer_ref}/proxy/messages/:id/read

Send a read receipt

REQUEST BODY
{"chat":"15550001234@s.whatsapp.net"}
RESPONSE
{"ok":true}
GET https://wabridges.com/api/instances/{customer_ref}/proxy/messages/:id/media

Download received media by message_id (cached 3h)

RESPONSE
(image/jpeg)

CONTACTS

GET 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.

RESPONSE
[{"about":"","business_address":"","business_email":"","business_name":"","first_name":"","full_name":"","is_business":false,"jid":"","name":"","phone":"","redacted_phone":"","registered":false,"verified_name":""}]
POST https://wabridges.com/api/instances/{customer_ref}/proxy/contacts/check

Check which phone numbers are on WhatsApp

REQUEST BODY
{"phones":["15550001234","00000000000"]}
RESPONSE
[{"is_business":false,"is_on_whatsapp":false,"jid":"","phone":"","registered":false,"verified_name":""}]
GET https://wabridges.com/api/instances/{customer_ref}/proxy/contacts/:jid

Get info for a single contact

RESPONSE
{"about":"","business_address":"","business_email":"","business_name":"","first_name":"","full_name":"","is_business":false,"jid":"","name":"","phone":"","redacted_phone":"","registered":false,"verified_name":""}
GET https://wabridges.com/api/instances/{customer_ref}/proxy/contacts/:jid/avatar

Download a contact's avatar

RESPONSE
(image/jpeg)

PROFILE

GET https://wabridges.com/api/instances/{customer_ref}/proxy/profile

Your own profile

RESPONSE
{"about":"","jid":"","name":"","phone":""}
PATCH https://wabridges.com/api/instances/{customer_ref}/proxy/profile/status

Set your About status text

REQUEST BODY
{"status":"Hey there!"}
RESPONSE
{"ok":true}
GET https://wabridges.com/api/instances/{customer_ref}/proxy/profile/avatar

Download your own avatar

RESPONSE
(image/jpeg)
PUT 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.

REQUEST BODY
{"url":"https://example.com/me.jpg"}
RESPONSE
{"picture_id":""}
DELETE https://wabridges.com/api/instances/{customer_ref}/proxy/profile/avatar

Remove your profile picture

RESPONSE
{"ok":true}
GET https://wabridges.com/api/instances/{customer_ref}/proxy/profile/qr

Personal QR link to share your contact

RESPONSE
{"link":"https://wa.me/qr/ABCDEFGHIJKL"}

PRIVACY

GET https://wabridges.com/api/instances/{customer_ref}/proxy/status/privacy

Get who can see your status updates

RESPONSE
[{"is_default":false,"list":[""],"type":"contacts"}]

PRESENCE

POST https://wabridges.com/api/instances/{customer_ref}/proxy/presence

Set your online/offline presence

REQUEST BODY
{"state":"unavailable"}
RESPONSE
{"ok":true}
POST 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.

REQUEST BODY
{"chat":"15550001234"}
RESPONSE
{"ok":true}

DEBUG

GET 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.

RESPONSE
{"ok":true}
GET https://wabridges.com/api/instances/{customer_ref}/proxy/debug/stats

Runtime stats (goroutines, heap, cache)

RESPONSE
{"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.

WEBHOOK 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?

EXAMPLE PAYLOAD
{"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"}
WEBHOOK connected

Fields: event (connected), phone

EXAMPLE PAYLOAD
{"event":"connected","phone":"15550001234"}
WEBHOOK disconnected

Fields: event (disconnected), detail?, reason (logged_out|stream_replaced)?

EXAMPLE PAYLOAD
{"detail":"401: logged out","event":"disconnected","reason":"logged_out"}
WEBHOOK typing

Fields: event (typing), contact_id, chat_id, state (composing|paused), mode (text|voice)

EXAMPLE PAYLOAD
{"chat_id":"15550001234@s.whatsapp.net","contact_id":"15550001234@s.whatsapp.net","event":"typing","mode":"text","state":"composing"}
WEBHOOK poll_vote

Fields: event (poll_vote), contact_id, phone, chat_id, poll_id, message_id, name, from_me, timestamp, selected_options, offline?

EXAMPLE PAYLOAD
{"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}
WEBHOOK 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?

EXAMPLE PAYLOAD
{"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}
WEBHOOK call_incoming

Fields: event (call_incoming), call_id, contact_id, platform, timestamp

EXAMPLE PAYLOAD
{"call_id":"ABCDEF123456","contact_id":"15550001234@s.whatsapp.net","event":"call_incoming","platform":"android","timestamp":1777180605}
WEBHOOK call_terminated

Fields: event (call_terminated), call_id, contact_id, reason (timeout|hangup|decline|busy|accept_elsewhere), timestamp

EXAMPLE PAYLOAD
{"call_id":"ABCDEF123456","contact_id":"15550001234@s.whatsapp.net","event":"call_terminated","reason":"hangup","timestamp":1777180720}
WEBHOOK offline_sync_preview

Fields: event (offline_sync_preview), total, messages, notifications, receipts, app_data_changes

EXAMPLE PAYLOAD
{"app_data_changes":0,"event":"offline_sync_preview","messages":120,"notifications":8,"receipts":14,"total":142}
WEBHOOK offline_sync_completed

Fields: event (offline_sync_completed), count

EXAMPLE PAYLOAD
{"count":142,"event":"offline_sync_completed"}
WEBHOOK profile_picture_updated

Fields: event (profile_picture_updated), remove, timestamp, picture_id?

EXAMPLE PAYLOAD
{"event":"profile_picture_updated","remove":true,"timestamp":1777180800}