WABridges
← Docs API REFERENCE
Quickstarts llms-full.txt

API Reference

Authentication

Every request takes your API key as a Bearer token. Keep it in an environment variable, never in a script. v1 updated July 2026

bash
export WA_API_KEY="sk_..."
http
Authorization: Bearer $WA_API_KEY

Create an account to get your API key.

Rate Limits

Limits are per API key. Going over returns 429 Too Many Requests with a Retry-After header saying how many seconds until the window resets.

limits · per API key · 60 s window
POST /api/instances (provision)10 req / min
/api/instances/:id/proxy/*120 req / min
/api/* (all other endpoints)120 req / min
All endpoints (per IP, global floor)300 req / min
← 429
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
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!"}'
Idempotency-Key · stored 24 h
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, a 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

Create, list, inspect and re-point a bridge. Every route takes Authorization: Bearer $WA_API_KEY.

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

Provision a new bridge on your subscription. If customer_ref already exists, returns the existing record. The subscription itself (and the 7-day free trial, card on file) starts when you connect your first number in the dashboard; the API adds bridges to it.

customer_refstring, requiredYour internal identifier for this bridge (e.g. user ID). Letters, digits, dashes, underscores.
webhook_urlstring, optionalURL that receives all inbound WhatsApp events as POST requests. Omit it to use the hosted inbox (events show on the dashboard); change it later with PATCH.
curl
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"
  }'
← 201
{
  "id": "7d09aa9c-2f4e-4b6a-9c1d-3e5f7a9b1c2d",
  "customer_ref": "user-123",
  "state": "running",
  "created_at": 1777180605,
  "webhook_secret": "whsec_...",
  "webhook_url": "https://yourbackend.com/hook"
}

Errors: 400 invalid_customer_ref / invalid_webhook_url · 402 no_subscription, the account has no subscription yet (connect a number in the dashboard first; the body carries dashboard_url and billing_url), or payment_required when payment is 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.

curl
curl https://wabridges.com/api/instances \
  -H "Authorization: Bearer $WA_API_KEY"
← 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. On this and every other bridge route, :customer_ref may be default: the account's first bridge, so a single-number integration never carries a name in the URL.

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

PATCH https://wabridges.com/api/instances/:customer_ref

Change where the bridge delivers webhooks. Send "" or null to switch to the hosted inbox. The bridge restarts to apply the change (a few seconds; the WhatsApp session is kept). Works for sandbox too and returns its webhook_secret.

webhook_urlstring or null, requiredNew endpoint, or blank for the hosted inbox.
curl
curl -X PATCH https://wabridges.com/api/instances/{customer_ref} \
  -H "Authorization: Bearer $WA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url": "https://yourbackend.com/hook"}'
← 200
{
  "id": "7d09aa9c-2f4e-4b6a-9c1d-3e5f7a9b1c2d",
  "customer_ref": "user-123",
  "state": "running",
  "webhook_url": "https://yourbackend.com/hook",
  "webhook_secret": "whsec_...",
  "inbox": false
}

Errors: 400 invalid_webhook_url · 404 unknown customer_ref · 409 bridge_paused / bridge_suspended (an idle auto-paused bridge is woken instead) · 502 orchestrator could not apply the change

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

curl
curl -N https://wabridges.com/api/instances/{customer_ref}/logs?tail=50 \
  -H "Authorization: Bearer $WA_API_KEY"
← 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

5 endpoints on the bridge proxy, addressed by your customer_ref.

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

Connection status

← response
{"jid":"","lid":"","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

11 endpoints on the bridge proxy, addressed by your customer_ref.

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. Set as_sticker to send image/webp media as a WhatsApp sticker.

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)

Calls

1 endpoint on the bridge proxy, addressed by your customer_ref.

POST https://wabridges.com/api/instances/{customer_ref}/proxy/calls/:id/reject

Declines the call identified by a call_incoming webhook while it is still ringing. The caller sees the call declined immediately instead of ringing out. Rejecting a call that already ended is not an error WhatsApp reports; the request still returns ok.

request body
{"contact_id":"15550001234@s.whatsapp.net"}
← response
{"ok":true}

Contacts

5 endpoints on the bridge proxy, addressed by your customer_ref.

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)
GET https://wabridges.com/api/instances/{customer_ref}/proxy/groups/:jid

Get metadata for a group chat (subject, topic, size)

← response
{"created":0,"jid":"","name":"","participants":0,"topic":""}

Profile

6 endpoints on the bridge proxy, addressed by your customer_ref.

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

1 endpoint on the bridge proxy, addressed by your customer_ref.

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

2 endpoints on the bridge proxy, addressed by your customer_ref.

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

2 endpoints on the bridge proxy, addressed by your customer_ref.

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. Respond 200 immediately and process asynchronously, the timeout is 10 seconds.

The webhook guide covers delivery behaviour, signature verification and handler examples in six languages.

EVENT message
event (message)message_idcontact_idphonechat_idnamebodytype (text|media|reaction|poll|location|event)media_type (|image|video|audio|voice|document|sticker)is_groupfrom_metimestampcaption?edit (edit|revoke|pin)?event_details?forwarded?is_broadcast?location?max_answers?mentions?offline?options?quoted?reaction?target_id?verified_business?
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"}
EVENT connected
event (connected)phone
payload
{"event":"connected","phone":"15550001234"}
EVENT disconnected
event (disconnected)detail?reason (logged_out|stream_replaced)?
payload
{"detail":"401: logged out","event":"disconnected","reason":"logged_out"}
EVENT typing
event (typing)contact_idchat_idstate (composing|paused)mode (text|voice)
payload
{"chat_id":"15550001234@s.whatsapp.net","contact_id":"15550001234@s.whatsapp.net","event":"typing","mode":"text","state":"composing"}
EVENT presence
event (presence)contact_idunavailablelast_seen?
payload
{"contact_id":"15550001234@s.whatsapp.net","event":"presence","last_seen":1777180605,"unavailable":true}
EVENT poll_vote
event (poll_vote)contact_idphonechat_idpoll_idmessage_idnamefrom_metimestampselected_optionsoffline?
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}
EVENT event_response
event (event_response)contact_idphonechat_idevent_idmessage_idnamefrom_metimestampresponse (going|not_going|maybe|unknown)response_time_msextra_guest_countoffline?
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}
EVENT call_incoming
event (call_incoming)call_idcontact_idplatformtimestamp
payload
{"call_id":"ABCDEF123456","contact_id":"15550001234@s.whatsapp.net","event":"call_incoming","platform":"android","timestamp":1777180605}
EVENT call_terminated
event (call_terminated)call_idcontact_idreason (timeout|hangup|decline|busy|accept_elsewhere)timestamp
payload
{"call_id":"ABCDEF123456","contact_id":"15550001234@s.whatsapp.net","event":"call_terminated","reason":"hangup","timestamp":1777180720}
EVENT offline_sync_preview
event (offline_sync_preview)totalmessagesnotificationsreceiptsapp_data_changes
payload
{"app_data_changes":0,"event":"offline_sync_preview","messages":120,"notifications":8,"receipts":14,"total":142}
EVENT offline_sync_completed
event (offline_sync_completed)count
payload
{"count":142,"event":"offline_sync_completed"}
EVENT profile_picture_updated
event (profile_picture_updated)removetimestamppicture_id?
payload
{"event":"profile_picture_updated","remove":true,"timestamp":1777180800}