One API key can run a WhatsApp number per customer - how to name them, address them, and what billing does as they come and go.
An account key is account-wide. Every bridge on the account is reachable with the same key, addressed by the customer_ref you gave it, so a platform putting a WhatsApp number behind each of its own customers needs one credential rather than a keyring.
That also means the key is worth guarding accordingly: it can send from every number you run, so it belongs on your server and never in an app, a browser, or a customer's hands.
customer_ref is any string you control - a user id, a tenant slug, an order number - made of letters, digits, dashes and underscores. Pick something you already have in your database, because it becomes the address in every later call and you will be looking it up constantly.
Creation is idempotent on that ref. Posting one that already exists returns the existing bridge with 200 instead of creating a duplicate, where a fresh one returns 201. That makes provisioning safe to retry, and safe to run on every tenant boot without a "does it exist" check first.
curl -X POST https://wabridges.com/api/instances \
-H "Authorization: Bearer $WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_ref": "tenant-4417",
"webhook_url": "https://yourbackend.com/hook/tenant-4417"
}'
{
"id": "7d09aa9c-2f4e-4b6a-9c1d-3e5f7a9b1c2d",
"customer_ref": "tenant-4417",
"state": "running",
"created_at": 1777180605,
"webhook_secret": "whsec_...",
"webhook_url": "https://yourbackend.com/hook/tenant-4417"
}
Two ways to know which bridge an event came from, and they suit different shapes of system.
A URL per bridge - /hook/tenant-4417 - puts the routing in the path, so your handler knows the tenant before it parses anything. One shared URL is less to manage, and you identify the bridge from the delivery instead. Either way each bridge has its own webhook_secret, so a shared endpoint must pick the right secret per bridge before it can verify the signature.
A single-bridge integration can skip naming entirely: default is accepted anywhere a customer_ref is and means the account's first bridge, so URLs never carry a name.
curl https://wabridges.com/api/instances \ -H "Authorization: Bearer $WA_API_KEY"
A bridge is a $5/month seat on the account's subscription, added when you create it and removed when you cancel it, with the unused days refunded automatically. Bridges created during the 7-day trial are free until the trial ends.
The subscription itself starts when you connect your first number in the dashboard, card on file. Until that has happened the API has nothing to add a seat to, and provisioning returns 402 with no_subscription - the body carries the dashboard URL to send an admin to. 402 payment_required is the other one: the subscription exists but payment is past due.
Accounts start capped at 10 bridges and a further create returns 403. That cap is a guard against runaway loops, not a sales gate - ask us and we raise it.
Provisioning is rate limited to 10 requests per minute. A platform onboarding many tenants at once should queue creates rather than firing them in parallel.
Something here not matching what you see? Write to us - a person answers.