Changelog
Every customer-visible change to the API and the portal, newest first.
Versioning and deprecation policy
The API is versioned in the URL: every endpoint lives under /v1.
Additive changes ship without prior notice. New endpoints, new optional request parameters, new fields inside existing responses, new values in existing lists and new error codes for new failure modes can appear at any time. Your integration must therefore ignore response fields it does not recognise rather than fail on them.
Nothing is removed or repurposed without 90 days' notice. No response field, request
parameter, endpoint or error code inside /v1 will be removed, renamed or given a new meaning
without a dated deprecation entry published on this page at least 90 days beforehand. The
meaning of an existing error code will not change either.
Breaking changes ship as a new version prefix. When a change cannot be made additively it
goes to a new prefix, and /v1 keeps working for the notice period announced here.
2026-09-29 — Restarts that recover a lost number, and a fresh month after a restart
Numbers (sessions)
POST /v1/sessions/{id}/restarton a number WhatsApp's engine no longer holds creates it again under the same id, instead of failing. It comes back waiting for a scan, with its phone unlinked.- A renewal restarts a number stopped for want of a subscription even when the engine had lost it. If the restart still fails, the portal tells you to restart or relink the number yourself.
- The daily check also restarts every number stopped for want of a subscription whose account is subscribed again, so none stays stopped after a renewal.
- A restarted number gets a fresh 30 days to be linked before it can be stopped as unlinked.
- Reactivating a deleted number now says how many messages it can send on its first day of warm-up.
2026-09-28 — Numbers stopped for want of a subscription come back on renewal
Numbers (sessions)
- A number stopped because the account had no subscription cannot be restarted, relinked or asked
for a QR or pairing code until a subscription is active again:
restart,logout,qrandpairing-codeanswer403 subscription_required, and the portal says why. A number stopped for staying unlinked can still be restarted, so it can be linked. - Renewing the subscription restarts those numbers by itself, and the portal tells you each one was restarted.
- A number stopped as idle no longer shows its stop reason after it reconnects on any path.
API reference
GET /v1/media/{message}andGET /v1/scheduled-messages/{id}document their real success response instead of an error example.
2026-09-28 — Number spellings, the phone after a relink, and idle numbers
Sending
- Every recipient field (
to, bulkrecipients, groupparticipants, and the suppressionphone) accepts any spelling of a number:+,00, spaces, dashes, dots, parentheses, Arabic-Indic, Persian or full-width digits, invisible direction marks, upper-case servers such as@C.USor@S.WhatsApp.Net, and:Ndevice suffixes. They all fold to one spelling, so an opt-out matches however the number was typed. A scheduledtoand a campaign recipient'sphoneare stored and returned in that spelling. - Refused now (
422 invalid_recipientfor a send or a schedule;skippedasinvalid_formatin a campaign;422 validation_failedfor group participants): a national number without its country code (leading0), text around a number, digits from other scripts, and servers other thanc.us,s.whatsapp.net,lid,g.us,newsletterandbroadcast. Such numbers reached nobody before.
Numbers (sessions)
- When
GET /v1/sessions/{id}or the periodic check sees a number connected before WhatsApp's own event arrives, it fillsphonestraight away. Theconnectedsession.statusevent is sent once per connection, whichever of them sees it first. - A number whose account has had no subscription for 30 days, or that has waited for a scan or stayed failed for 30 days, is now stopped. It is never deleted: its id, settings and history stay, and a restart or a relink brings it back. The portal shows why it was stopped.
Bot
- WhatsApp Business greeting and away messages no longer pause the bot, so a new customer gets an answer. A reply typed on the phone within 10 seconds of the customer's message does not start a pause, but it does extend one that is already running.
2026-09-27 — Late receipts, input limits and human takeover
Delivery
- A receipt WhatsApp sends before the send's own answer arrives, or under another spelling of the
chat (such as a
@lidchat), is no longer lost: it reachesmessage.status. - A message that failed with
delivery_unconfirmedbecomessent, thendeliveredorread, when WhatsApp confirms it later. Itserrorbecomesnull, usage counts it as sent instead of failed, and a campaign counts it as sent. message.statusonly moves forward: never fromreadback todelivered, and a failed receipt never followsdeliveredorread. A send WhatsApp rejects before its own answer arrives fails withsend_failed.
Bot
- Human takeover works: a reply typed on the phone or on WhatsApp Web pauses the bot in that chat
for the configured minutes. It never triggered before. Messages sent through the API or by the
bot never pause it, and phone-sent messages are still not forwarded as
message.received.
Numbers (sessions)
- New endpoint:
POST /v1/sessions/{id}/reactivatebrings a deleted number back under the same id, waiting for a scan. It takes a plan slot like creating a number, and needssessions:writeand an active subscription. 409 session_state_conflictcarriesdetails.reasonnext todetails.status:linked,not_ready,stopped,deletedornot_deleted.GET /v1/sessions/{id}checks the status with WhatsApp's engine as it is read, and falls back to the last known status after about 3 seconds. The list still shows the stored status.- Session objects gain
deleted_at. - Deleted numbers now really send no status events: despite the entry of 2026-09-26, the periodic
check still sent one after a delete. The
session.statusevents that check sends includephone.
Sending
text,captionand an edited message'stextare limited to 65,536 characters.- A contact send takes 1–50 cards (
fullName,phoneNumber,organization,whatsappId, or a rawvcard). Each field is at most 1,024 characters andvcardat most 65,536; unknown keys are refused. - A group's subject is at most 100 characters and its description at most 2,048.
- Over a limit is
422 validation_failednaming the field. - A
…@s.whatsapp.netrecipient, with or without a:Ndevice suffix, is accepted and stored as…@c.us. Opt-outs match that spelling in campaigns and scheduled sends, and thechat_idfilter folds it the same way. Group participants with a device suffix are treated as the plain number. - One campaign runs per number at a time, now enforced under a lock. A campaign started or resumed
while another runs on the same number waits as
pending; resuming one that is already running is409 invalid_bulk_state.
Webhooks
POST /v1/webhooks/{id}/deliveries/{delivery}/retryanswers409 webhook_delivery_not_retryableunless the delivery's status isfailed.
Account and API keys
GET /v1/usageaddssubscription_active. Without an active subscription,remainingis 0 for both windows instead ofnull.- New and rotated keys look like
<id>|wd_live_…(live) or<id>|wd_test_…(test). Existing keys are unchanged and keep working. Treat a key as opaque.
Portal and sign-in
- A flood of sign-ups can no longer block verification codes for existing customers: codes sent to their verified numbers have their own budget.
2026-09-26 — Number lifecycle and API key allowlists
Numbers (sessions)
- New endpoint:
POST /v1/sessions/{id}/logoutunlinks the phone and keeps the session: same id, same number slot. The session then waits for a new QR scan or pairing code. A number linked afterwards starts its warm-up again. qr,pairing-code,restartandlogoutcheck the session's state first. When the action makes no sense in that state, they answer409 session_state_conflictwithdetails.statusinstead of asking WhatsApp.- New ability
sessions:connectcoversqr,pairing-code,restartandlogout.sessions:writestill includes it, so existing keys keep working. - The pairing-code phone must be an international number; anything else is
422 validation_failed. - A failed number now counts against your plan's number limit until it is deleted or relinked. A number whose creation failed gives its slot back.
- A late
session.statusevent can no longer move a session's status backwards, and deleted numbers send no more status events. - A session's egress proxy is checked again regularly. One found pointing at a private or internal address on two checks in a row has its session stopped and the proxy removed.
API keys
- A key's IP allowlist accepts single addresses and CIDR ranges, for IPv4 and IPv6. It can be set and edited in the portal.
- A refused call's message names the IP it came from.
Portal
- Re-link links a phone to an existing number under the same id, with a QR code or a pairing code.
- Log out number unlinks the phone and keeps the id.
- Reactivate brings a deleted number back under its id when your plan has a free slot.
- Delete number says plainly that the id, and every integration built on it, stop working.
2026-09-25 — Delivery confirmation, exact quota and account safety
Delivery
- Every send now carries its own id. When WhatsApp's engine took a send but its answer was lost,
the message stays
queuedwhile delivery is confirmed, for up to about 30 minutes. It then becomessentor fails withdelivery_unconfirmed, and it is never sent twice. A message confirmed this way emitssentand thendeliveredorread. - A failure's
erroris now a stable code, never raw error text. This applies to messages, to campaign recipients'error, to scheduled messages'last_errorand to themessage.statuswebhook. Examples:upstream_timeout,upstream_unreachable,delivery_unconfirmed,media_too_large,suppressed,account_suspended; the full list is under List messages. Stored values were rewritten to these codes.
Quota, limits and keys
quota_exceeded,daily_cap_reachedandquota_insufficientnow count messages still queued and the unsent recipients of open campaigns, so a burst can no longer overshoot the plan. A paused campaign keeps holding its recipients' quota until it finishes or is canceled.GET /v1/usagegainstoday.reservedandmonth.reserved.remainingand theX-Quota-*headers are net of them.- The API rate limit applies to the account as a whole, separately for live and test keys, and no longer to each key.
- At most 10 active API keys per account. An expired or revoked key can no longer be rotated: create a new one. An expiry date in the past is refused.
Idempotency
- An
Idempotency-Keyis also bound to the session it was first used on. Reusing it on another session, or across a test and a live key, returns409 idempotency_key_reused. - Scheduled sends now compare the request body too.
Validation
- A scheduled send with an invalid recipient is refused with
422 invalid_recipientwhen it is scheduled. PUTandDELETE /v1/messages/{id}refuse a received message with422 validation_failed, and an outbound message that was never sent with409 message_not_sent.- Polls need a name of up to 255 characters and 2 to 12 unique options of up to 100 characters
each; anything else is
422 validation_failed.
Groups
- Adding participants, and creating a group, take at most 20 people per request. Removing, promoting and demoting take at most 1,024. Every id is validated.
- Suppressed numbers are skipped and listed in
skipped(meta.skippedon create). - Each number has a daily budget of participant additions. Past it the answer is
429 daily_cap_reachedwithdetails.scope: group_adds. - Participant changes return WhatsApp's answer for each person:
{ok, participants, skipped}.okis true only when everyone succeeded. - Group list and get now return
id,subject,ownerandparticipants_counton the engine this service runs; they were empty before.
Inbound messages and opt-outs
- In
message.receivedand the messages API,fromis the sender's phone chat id whenever WhatsApp reveals it. A newfrom_lidfield carries the sender's@lidid when the message was addressed that way. The messages export gains the same column. - Suppressions may hold
…@lidids.POST /v1/suppressionsaccepts them and refuses group, channel and broadcast ids with422.DELETEremoves every form of the person. - Adding a suppression skips that person in pending campaigns, and campaigns check each
recipient again right before sending (skipped as
suppressed). A scheduled message to a suppressed number fails withsuppressed.
Account suspension
- A suspended account sends nothing:
- API sends are refused with
403 account_suspended; - running campaigns pause with
account_suspended; - scheduled messages that come due fail with
account_suspended; - the bot stays silent.
- API sends are refused with
- The account can still list, read and cancel its campaigns and scheduled messages through the API.
Sessions
session.statusno longer repeatsconnectedfor the same connection.- WhatsApp's new passkey pairing steps are reported as
scan_qr. - The number-check endpoint returns the phone chat id even when WhatsApp answers with a
@lid.
Billing
- Paid plans keep full service through their grace period after the end date.
- The portal sends renewal reminders 7, 3 and 1 day(s) before a subscription ends, and a notice when the grace period starts. During grace, the subscription card says when service stops.
2026-09-25 — Safer sending and stricter input
Links and media
-
Plain-text messages no longer get an automatic link preview, and neither does an edited message. For a preview card, send
type: link_preview. -
A file given by URL (
media.url,preview.image.url, andfile.urlon profile and group pictures) is now downloaded by the platform just before it is sent, and handed to WhatsApp as the file itself. For scheduled and campaign sends that is at send time, not when you made the request. A campaign downloads its file once for all its recipients. -
The URL must be
httporhttpsand its host must resolve to a public address. Any other URL is refused with422 validation_failed. Up to three redirects are followed, and each one is checked the same way. The download must finish within 20 seconds. -
Size limits: 16 MB for message media, and 5 MB for profile and group pictures and link-preview images. They apply to a downloaded file and to inline
dataalike. -
The file's type must suit the message:
- image: JPEG, PNG, WebP or GIF;
- video: any video type;
- voice: any audio type;
- document: any type.
-
A message whose file cannot be used fails, with one of these stable values in its
error(on the message and in themessage.statuswebhook):media_url_blockedmedia_too_largemedia_type_not_allowedmedia_unavailable
A temporary network or server error is retried before the message fails. A picture that cannot be used is refused at once with
422 validation_failed. -
preview,buttonsandlistaccept only their documented keys. Any other key is refused with422 validation_failed. -
A session's delay between sends is capped at 10 seconds.
Webhooks
- A webhook URL whose host does not resolve to a public address is refused with
422 validation_failedwhen you save it, instead of being saved. - Each delivery goes only to the address that was checked, and redirects are no longer
followed. A
3xxanswer counts as a failed attempt. - Webhook deliveries now carry an
errorfield. It is set for a delivery that was never attempted, to one of:blocked_addressunresolvable_hostinvalid_hostinvalid_urlscheme_not_alloweduserinfo_not_allowedendpoint_inactive
Sessions
proxy.servermust behost:portwith a public host. It takes no scheme, path or credentials.proxy.usernameandproxy.passwordmust be sent together, and use only the characters listed in the reference.- Any other key under
proxyis refused.
Errors and input
- New error codes:
invalid_cursor(422): acursorthis list did not issue;invalid_encoding(400): a value that is not valid UTF-8 or contains a NUL character.
to: "status@broadcast"is refused withinvalid_recipient.- An
Idempotency-Keylonger than 255 characters, or containing control characters, is refused with422 validation_failed. - A path id that is not a number now returns
404 not_foundinstead of a server error. - WhatsApp Status updates posted by your contacts are no longer treated as messages. They are not stored and not sent to your webhooks, and the ones already stored have been removed.
Portal
- Sign-in and verification codes are rate-limited per account, per phone number and per network. After too many wrong codes the portal says so and when to try again, and sends no new code until then.
- Phone numbers are stored in international form, so the same number cannot be registered twice under two spellings.
2026-08-15 — Official PHP and Node clients
Two hand-written API clients are now published:
composer require whatsdev/whatsdev-php
npm install @whatsdev/sdk
Both cover the endpoints an integration touches daily and reach the rest through a generic
request() method. Each carries a typed exception per error code, attaches an Idempotency-Key
to every send so a dropped connection cannot deliver twice, iterates paginated lists, exposes the
remaining-quota headers on send results, and verifies webhook signatures in constant time. Neither
has any runtime dependency; the PHP package auto-registers a Laravel facade when Laravel is present.
Nothing about the API changed — these are clients for the surface already documented.
2026-08 — Your data: export, deletion and retention
Export a copy of your data
- New in the portal. Your profile page can now build a zip archive of the account: account and
subscription details, users, messages, contacts and their custom fields, lists, scheduled
messages, campaigns, templates, suppressed numbers, sessions, webhook endpoints, payments and
API-key metadata, as CSV files with a
manifest.jsonthat names every category left out of the archive and why. - It carries no credential. An API key exists only as an irreversible hash, and webhook signing secrets, session proxy credentials and password hashes are never exported.
- One archive every 24 hours. You are notified when it is ready, it is downloadable only by a signed-in user of that same account and never from a public link, and it is deleted 7 days after it is produced.
Delete your account
- New in the portal. You can close the account yourself, from the same page.
- The moment you confirm, access ends: every API key is revoked, every WhatsApp number is disconnected and handed back, and every pending scheduled message and running campaign is cancelled. None of that is restored if you change your mind.
- Everything else the account holds, stored files included, is erased permanently 14 days later. Until then you can cancel the request from the portal and the account is reopened.
- Payment records are kept in anonymised form for accounting: they are detached from the account and stripped of the fields that could name it, and cannot be traced back to it afterwards.
Sandbox retention
- Sandbox sessions are deleted once they pass the sandbox retention window, and the messages that belong to them are deleted with them. It is a session age limit, not a blanket age limit on sandbox data: a sandbox session you keep alive keeps its messages.
2026-08 — Sandbox and cursor pagination
Sandbox (test mode)
- New accounts are issued a test API key at signup. A test key needs no active subscription and carries its own rate-limit budget, separate from your plan's.
- Sandbox sessions run against a simulated WhatsApp. They connect on their own, take no slot from your plan's session allowance, and are never attached to a real WhatsApp account.
- Sends made with a test key go through the ordinary send endpoint and return the ordinary response, but never reach WhatsApp and never count against your plan's message quota. A separate daily sandbox limit applies to them instead.
- New endpoint:
POST /v1/sandbox/inboundinjects a simulated inbound message through the same pipeline a real one travels, so your webhook receiver, its signature check and your auto-replies can be exercised end to end without asking anyone to message you. - Test and live are kept apart in both directions: a test key is refused on a live session and a
live key is refused on a sandbox session, and
GET /v1/sessionsreturns only the sessions matching the calling key's mode. - A simulated opt-out ("إيقاف") is detected and answered with the same confirmation reply, which
travels the simulated transport and so reaches nobody. The one difference is that it is never
written to your real suppression list: that list is account-wide rather than per-session, so a
simulated opt-out must never block a real number from your live sending. For the same reason a
test key cannot change that list (
POSTandDELETE /v1/suppressions), though it may read it. - A webhook created with a test key must be scoped to a sandbox session, so
whatsapp_session_idis required with one: a webhook with no session is account-wide and receives your live sessions' events, bodies and numbers included, which no test key may reach. - Sandbox sessions — and the messages that belong to them — are deleted once they pass the sandbox retention window. Nothing outside the sandbox is touched by that sweep.
Cursor pagination
?cursor=is now accepted on the session-message, scheduled-message, contact, bulk-operation, bulk-recipient and webhook-delivery lists.- It is opt-in and additive. Leave
cursoroff the request and page-based pagination is unchanged: same ordering, samemeta, same links. - It exists for traversal stability, not for speed. Walking a list by page number while new rows are still arriving repeats some rows and skips others; a cursor walk does not.
- In cursor mode the response carries
meta.next_cursorandmeta.prev_cursorin place ofmeta.total,meta.current_page,meta.last_page,meta.fromandmeta.to, andlinks.firstandlinks.lastare null. Followlinks.next, which preserves the filters andper_pageyou sent.
Portal
- The message list hides sandbox messages by default and offers a filter to show them, so test traffic never clutters the real inbox.