A bridge is one WhatsApp number, connected as a linked device, with a REST API in front of it and a webhook behind it. We keep the session online. You write the code that decides what to say.
Your app talks REST to the bridge. The bridge talks WhatsApp. Everything inbound comes back to your webhook URL.
Every WhatsApp number you connect gets its own bridge. Think of it as the phone that stays logged in, except it lives on our servers and answers to HTTP.
customer_refYou pick the ID when you create it. Every later call and every webhook carries it.Every call takes your account API key as Authorization: Bearer sk_…. One is created at signup; make more from the dashboard.
POST with a customer_ref you control (a user ID, an order number, any string) and the webhook URL that should receive events. The ref you choose addresses the bridge in every later call.
Provisioning over the API adds bridges to an existing subscription. Create the first bridge from the dashboard (one click, 7-day free trial with a card on file); everything below works the same either way.
{
"customer_ref": "user-123",
"webhook_url": "https://your-app.com/hook"
}
← {
"id": "7d09aa9c-…",
"customer_ref": "user-123",
"state": "running"
}
Scan the QR in the dashboard, or call /proxy/pair to get an 8-character code. In WhatsApp: Linked devices › Link a device › Link with phone number. Once paired, GET /proxy/status returns "status": "connected" with the number and display name.
0:58 to live{ "phone": "15550001234" }
← { "code": "ABCD-EFGH" }
POST to /proxy/send/text with the destination number and the body. You get back a message ID and a Unix timestamp. The same proxy sends media, polls, reactions, locations and more; the API reference lists every endpoint.
{
"chat": "15559876543",
"body": "Hello from my app!"
}
← {
"message_id": "ACE41E...",
"timestamp": 1777180605
}
Every inbound message, delivery receipt, call and status change is POSTed to your webhook URL as it happens. Reply with 200 OK to acknowledge. Put your model behind this handler and you have an agent. Every event type is in the API reference.
{
"type": "message",
"from": "15559876543",
"body": "Hey, got your message!",
"timestamp": 1777180621,
"message_id": "BD71F2...",
"customer_ref": "user-123"
}
disconnectedinstantlySessions can drop: a phone wiped, a number moved, WhatsApp logging out a linked device. Your app finds out from us, not from your users.
While a bridge is down, sends return an error instead of silently dropping, and inbound messages still land on the phone as normal because the bridge is just a linked device.
Worried about bans? The honest answer is on the pricing page →
Send with any HTTP client. Replace CUSTOMER_REF with the ref from step 1 and API_KEY with the key from your dashboard.
curl -X POST \ https://wabridges.com/api/instances/$CUSTOMER_REF/proxy/send/text \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"chat":"15559876543","body":"Hello!"}'
const res = await fetch( `https://wabridges.com/api/instances/${CUSTOMER_REF}/proxy/send/text`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ chat: '15559876543', body: 'Hello!', }), } ); const { message_id, timestamp } = await res.json();
import requests res = requests.post( f"https://wabridges.com/api/instances/{CUSTOMER_REF}/proxy/send/text", headers={"Authorization": f"Bearer {API_KEY}"}, json={"chat": "15559876543", "body": "Hello!"}, ) data = res.json() # {"message_id": "ACE41E...", "timestamp": 1777180605}
$ch = curl_init("https://wabridges.com/api/instances/$customerRef/proxy/send/text"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer $apiKey", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode(["chat" => "15559876543", "body" => "Hello!"]), ]); $data = json_decode(curl_exec($ch), true); // ["message_id" => "ACE41E...", "timestamp" => 1777180605]
require "net/http" require "json" uri = URI("https://wabridges.com/api/instances/#{customer_ref}/proxy/send/text") res = Net::HTTP.post( uri, { chat: "15559876543", body: "Hello!" }.to_json, "Authorization" => "Bearer #{api_key}", "Content-Type" => "application/json" ) data = JSON.parse(res.body) # {"message_id"=>"ACE41E...", "timestamp"=>1777180605}
payload, _ := json.Marshal(map[string]string{"chat": "15559876543", "body": "Hello!"}) req, _ := http.NewRequest("POST", "https://wabridges.com/api/instances/"+customerRef+"/proxy/send/text", bytes.NewReader(payload)) req.Header.Set("Authorization", "Bearer "+apiKey) req.Header.Set("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) // {"message_id":"ACE41E...","timestamp":1777180605}
let res = reqwest::blocking::Client::new() .post(format!("https://wabridges.com/api/instances/{customer_ref}/proxy/send/text")) .bearer_auth(api_key) .json(&serde_json::json!({ "chat": "15559876543", "body": "Hello!" })) .send()?; let data: serde_json::Value = res.json()?; // {"message_id":"ACE41E...","timestamp":1777180605}
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://wabridges.com/api/instances/" + customerRef + "/proxy/send/text"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"chat\":\"15559876543\",\"body\":\"Hello!\"}"))
.build();
HttpResponse<String> res =
HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
// {"message_id":"ACE41E...","timestamp":1777180605}
Want the full tutorial with webhook handling? See the step-by-step quickstarts: Node.js, Python, PHP, Ruby, Go, Rust, Java.