Run these in order. Your first bridge is free for 7 days with a card on file, nothing charged until the trial ends.
Create an API key at Dashboard → API keys. It starts with sk_ and is shown once, so copy it now. Every call below sends it as Authorization: Bearer $WA_API_KEY.
No dependencies required. The examples use java.net.http.HttpClient (Java 11+) and org.json for JSON. Add both to pom.xml.
customer_ref = sandbox. Skip steps 2 and 3, use sandbox wherever a customer_ref appears below, and send to 15550001234, a fake contact who replies within seconds. Events show on your dashboard.<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.9.4</version>
</dependency>
export WA_API_KEY="sk_..."
Your first bridge is created from the dashboard: one click connects a number and starts the 7-day free trial (card on file, $0 today). Every API call below can address it as /api/instances/default/…: default always means your first bridge, so no name goes in the URL. The call here is how you add more bridges to the subscription.
customer_ref is your internal identifier (user ID, slug, anything you control). webhook_url is optional: leave it out to use the hosted inbox and read events on the dashboard, change it any time with PATCH.
Store the webhook_secret that comes back. It signs every webhook the bridge sends you.
402 no_subscription: connect a number in the dashboard first.import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import org.json.JSONObject;
public class WaBridges {
static final String API_KEY = System.getenv("WA_API_KEY");
static final String BASE = "https://wabridges.com/api";
static final HttpClient HTTP = HttpClient.newHttpClient();
static JSONObject waPost(String path, JSONObject body) throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(BASE + path))
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build();
HttpResponse<String> res = HTTP.send(req, HttpResponse.BodyHandlers.ofString());
return new JSONObject(res.body());
}
public static void main(String[] args) throws Exception {
JSONObject bridge = waPost("/instances", new JSONObject()
.put("customer_ref", "user-123")
.put("webhook_url", "https://yourbackend.com/hook"));
System.out.println(bridge);
}
}
{"id": "7d09aa9c-…", "customer_ref": "user-123", "state": "running", "created_at": 1777180605, "webhook_secret": "whs_…", "webhook_url": "https://…/hook"}
You need the phone in hand for this step. Request a pairing code, open WhatsApp on the phone, go to Linked Devices → Link a device → Link with phone number, then enter the code. Any number works, personal, VoIP, or a spare SIM. No phone? Use sandbox.
Once paired, the bridge fires a connected webhook event. Poll /proxy/status or wait for the event.
JSONObject result = waPost("/instances/user-123/proxy/pair",
new JSONObject().put("phone", "15550001234"));
System.out.println("Enter this code on the phone: " + result.getString("code"));
{"code": "ABCD-EFGH"}
Add an Idempotency-Key: <unique-per-message> header in production so a timeout plus retry can never double-send. See Idempotency.
A bridge that sat for a few hours with no phone linked is paused to save resources. The first call after that returns 503 bridge_starting with a Retry-After header. Wait those seconds and retry once.
JSONObject msg = waPost("/instances/user-123/proxy/send/text",
new JSONObject()
.put("chat", "15559876543")
.put("body", "Hello from the API!"));
System.out.println(msg.getString("message_id"));
{"message_id": "ACE41E...", "timestamp": 1777180605}
Inbound messages and events are delivered by POST to your webhook_url. Return 200 immediately and do the work afterwards.
The webhook guide has every event type and field, plus signature verification.
import static spark.Spark.*;
import org.json.JSONObject;
public class WebhookServer {
public static void main(String[] args) {
port(3000);
post("/hook", (req, res) -> {
res.status(200);
JSONObject payload = new JSONObject(req.body());
if ("message".equals(payload.optString("event")) && !payload.optBoolean("from_me")) {
System.out.printf("%s: %s%n",
payload.optString("name"),
payload.optString("body"));
}
return "";
});
}
}
Everything the quickstart skipped: media, polls, contacts, presence, and every webhook payload.