API v2 (beta)
Moving from v1 to v2
API v2 does what v1 does, with safer keys, one URL style, real HTTP status codes and signed JSON webhooks. This page maps each v1 endpoint and field to its v2 equivalent. What's new in v2 itself is in the overview.
What changes everywhere#
- Keys: v1's
client_id+api_keybecome one header,Authorization: Bearer wbz_live_…. Make a v2 key on Settings → API keys, in the API v2 keys card, with only the scopes the app needs. v1 keys don't work on v2, and v2 keys don't work on v1. - JSON only: no form fields. File uploads go to
POST /media(multipart) and are sent bymedia_id. - Methods and URLs:
GETto read,POSTto create,PATCHto change,DELETEto cancel or delete, underhttps://wbiztool.com/api/v2. - HTTP status codes instead of
"status": 0or1(see Responses and errors). - Phone numbers in international format with
+, nocountry_code. - Times in ISO 8601 with an offset, instead of separate date, time and timezone fields.
- Webhooks are endpoints you add once for the workspace, with signed JSON events, instead of a
webhookURL on each message.
Endpoint by endpoint#
v2 paths are relative to https://wbiztool.com/api/v2.
Sending, field by field#
| v1 | v2 | Notes |
|---|---|---|
client_id + api_key (body or Authorization: Bearer) | Authorization: Bearer wbz_live_… | A new v2 key; no client_id |
phone + country_code | to.phone | One number in E.164 with +: "9876543210" with "country_code": "91" becomes {"phone": "+919876543210"}. No country code is added for you |
phone with an @username (Telegram) | to.username | @name, t.me/name or tg: and an id, from a Telegram account |
group_name | to.group | The group's exact name |
| (not in v1) | to.contact_id | A saved contact |
whatsapp_client | from | The number's id; optional when one number is connected, as before |
msg (or message) | text | Up to 3,000 characters, as before |
msg_type 0, 1, 2 | none | A message with media is a media message; media.type (image or file) when the link doesn't say |
img_url / file_url | media.url | A public http(s) link |
file (upload in the same request) | media.media_id | Upload first, then send the id as often as you like |
file_name | media.file_name | For files |
schedule_msg/'s date + time + timezone | send_at | One time with its offset: 24/12/2026, 10:30 and Asia/Kolkata become "2026-12-24T10:30:00+05:30" |
expire_after_seconds | expires_at | A time, not a duration: now plus the seconds, with its offset |
webhook | webhook endpoints | Added once with POST /webhooks, for every message of the workspace |
msg_id in the answer | id | The Message itself comes back, with its status |
The same send in v1 and v2:
# v1
curl -X POST https://wbiztool.com/api/v1/send_msg/ \
-H "Authorization: Bearer YOUR_V1_KEY" \
-H "Content-Type: application/json" \
-d '{"whatsapp_client": 101, "msg_type": 1, "country_code": "91", "phone": "9876543210",
"img_url": "https://example.com/offers/diwali-sale.jpg", "msg": "Our Diwali sale starts today",
"expire_after_seconds": 3600}'
# v2
curl -X POST https://wbiztool.com/api/v2/messages \
-H "Authorization: Bearer $WBIZTOOL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: diwali-9876543210" \
-d '{"from": 101, "to": {"phone": "+919876543210"},
"media": {"url": "https://example.com/offers/diwali-sale.jpg"}, "text": "Our Diwali sale starts today",
"expires_at": "2026-10-20T11:30:00+05:30"}'
A few v1 behaviours are gone on purpose:
- No guessing of country codes. v1 put
country_codein front of a number (with91, always in front of a 10-digit one). v2 takes the number exactly as written with+. - No file download when you call. v1 fetched
img_urlandfile_urlduring the request. v2 checks that the link is public and fetches the file when the message is sent; a bad link shows up as afailedmessage. - No failed row on refusal. When v1 refuses a send over the daily 1,000, it still stores the message as failed. v2 stores nothing when it refuses a send.
- Unknown fields are refused instead of being ignored.
Statuses#
v1's numbers and words become one status field:
| v1 | v2 status |
|---|---|
0 Created / Pending | queued, or scheduled while its send_at is ahead |
1 Sent | sent, delivered or read (the WhatsApp ticks) |
2 Failed | failed |
3 Cancelled | cancelled |
4 Expired | expired |
Webhook events are message.sent, message.failed, message.cancelled and message.expired, matching v1's SENT, FAILED, CANCELLED and EXPIRED.
Responses and errors#
v1 answers many errors with HTTP 200 or 400 and "status": 0, and success with "status": 1. v2 uses the HTTP status code:
- Success is
200,201(created) or202(queued), and the body is the object itself, such as the Message. There's nostatus: 1wrapper. - Errors are
4xxor5xxwith one shape:{"error": {"code", "message", "field", "docs", "request_id"}}. Branch oncode, not on the Englishmessage. Every code is on the errors page.
# v1
result = response.json()
if result.get("status") == 1:
msg_id = result["msg_id"]
# v2
if response.status_code == 202:
message_id = response.json()["id"]
else:
error = response.json()["error"]
if error["code"] == "number_daily_cap_reached":
... # try again tomorrow
Some v1 messages and their v2 codes:
| v1 message | v2 code |
|---|---|
Auth Error: invalid api key | 401 invalid_api_key, api_key_revoked or api_key_expired |
Invalid phone number / Invalid Contact Number "…" | 422 invalid_phone |
Msg cant be null | 400 text_required |
Message length is too long | 400 text_too_long |
Invalid whatsapp client | 422 number_not_found |
Invalid whatsapp client id. | 422 from_required |
Not enough credits | 402 no_credits |
Daily limit reached: … | 429 number_daily_cap_reached |
Demo Account can not access apis | 403 demo_account |
Account Disabled | 403 account_disabled |
Invalid JSON format: … | 400 invalid_json |
Webhooks#
In v1, each message can carry a webhook URL that gets a form-encoded msg_id=…&status=SENT. In v2:
- Add an endpoint once with
POST /webhooksand store itssecret. - It gets JSON events for every message of the workspace, with the whole Message in
data.message. Match them to your records bydata.message.id. - Verify the signature: the same scheme as v1's (
WbizTool-Timestamp,WbizTool-Signature: v1=…), but with the endpoint's own secret instead of the workspace's. - Drop repeats with
WbizTool-Event-Id. Retries go on for about 23 hours, not 4 tries over a few minutes as in v1, and4xxanswers are retried too.
v1's connect events (webhook_url on whatsapp/connect/) have no v2 event yet: poll GET /numbers/{id} instead.
A checklist#
Make a v2 key per app
On Settings → API keys, with only the scopes it needs. Try it first with a test key, which checks everything and sends nothing.
Change the requests
New URLs and methods, JSON bodies,
to/from/text/media, phone numbers with+, times with offsets. Add anIdempotency-Keyto each send.Change the error handling
Check the HTTP status, then
error.code. Handle429by waiting forRetry-After.Move webhooks
Add an endpoint, verify signatures with its secret, and drop repeats by event id.
Switch over and revoke
Once the app runs on v2, delete its v1 key on Settings → API keys.
