How to leave a group in PHP with the Wapito WhatsApp API
A script, a token and an HTTP client is the whole stack. Everything below runs from a file you can execute today, and the same code moves to a worker or a container without changing shape.
For PHP the group cleanup is a CLI script that walks the list with Guzzle, reads each group's roles, hands over ownership where necessary and posts to /leave last. The export of the transcript has to be written to disk before that final request, and Guzzle's ClientException on a 404 is the ordinary signal that the group already dropped the number.
Before you start
composer require guzzlehttp/guzzlegetenv('WAPITO_TOKEN')- A channel with a WhatsApp number linked to it, and its API token. Create one in the dashboard — the authentication guide shows where the token goes.
How it works
Find the groups to leave
Work from the live list rather than from your own records, so an automation cleaning up after itself does not try to leave groups it was already removed from and log a pile of harmless 404s.
<?php $client = new \GuzzleHttp\Client(); $response = $client->request('GET', 'https://api.wapito.com/v1/groups?count=50&offset=0', [ 'headers' => [ 'Authorization' => 'Bearer wpt_YOUR_TOKEN', ], ]); echo $response->getBody();In PHP page the list with a do-while over Guzzle GETs and collect the ids with array_column; array_intersect against your own list of ids to leave yields only the groups the number is still in. A 32-bit build must keep those ids as strings, and array_intersect compares them as strings anyway.
API reference for this stepHand over first if you are the creator
Read the roles. If the linked number is the group's creator, promote a human admin before leaving, otherwise the group is stranded with nobody able to change its settings or membership.
<?php $client = new \GuzzleHttp\Client(); $response = $client->request('GET', 'https://api.wapito.com/v1/groups/120363041234567890@g.us', [ 'headers' => [ 'Authorization' => 'Bearer wpt_YOUR_TOKEN', ], ]); echo $response->getBody();In PHP fetch the group and search the participants array for the entry whose role is superadmin; if its id is the linked number, send a promotion POST for a human admin and check the decoded response before continuing. Put that condition in an if with an early continue rather than trusting a comment.
API reference for this stepLeave the group
Leaving is immediate and announced to the group. The group becomes invisible to the linked number afterwards, so store anything you still need - the id, the membership, the transcript - before you call this.
<?php $client = new \GuzzleHttp\Client(); $response = $client->request('POST', 'https://api.wapito.com/v1/groups/120363041234567890@g.us/leave', [ 'headers' => [ 'Authorization' => 'Bearer wpt_YOUR_TOKEN', ], ]); echo $response->getStatusCode();In PHP the departure is $client->request('POST', $baseUrl . '/groups/' . $id . '/leave') with no body option, inside a try that catches GuzzleHttp\Exception\ClientException and treats a 404 as already-left. Call file_put_contents for the export before this request and check its return value, since a false there means nothing was saved.
API reference for this stepConfirm on the webhook
The departure arrives as a participant event, which is the signal to archive your own record rather than assuming the call succeeded and moving on.
Arrives on your webhook as
groups.participants.In PHP the Slim handler decodes the participants event and, when $payload['data']['participant'] is the linked number, updates the group row to archived; return the 200 first and leave the database write to a queued job. This event is the real confirmation, and the POST's status is only a hint.
The whole script
Every step above in one runnable file. Save it as leave-group.php, put your token in the environment, and run it.
<?php
// Leave Group with the Wapito WhatsApp API.
//
// List what you are in, hand over the creator role if you hold it, leave, and archive your record when the webhook confirms it.
//
// Run it with:
// export WAPITO_TOKEN="wpt_..."
// php leave-group.php
require __DIR__ . '/vendor/autoload.php';
$baseUrl = 'https://api.wapito.com/v1';
$token = getenv('WAPITO_TOKEN');
// --- Find the groups to leave ---
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', $baseUrl . '/groups?count=50&offset=0', [
'headers' => [
'Authorization' => 'Bearer ' . $token,
],
]);
echo $response->getBody();
// --- Hand over first if you are the creator ---
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', $baseUrl . '/groups/120363041234567890@g.us', [
'headers' => [
'Authorization' => 'Bearer ' . $token,
],
]);
echo $response->getBody();
// --- Leave the group ---
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', $baseUrl . '/groups/120363041234567890@g.us/leave', [
'headers' => [
'Authorization' => 'Bearer ' . $token,
],
]);
echo $response->getStatusCode();
Receive the webhook
Slim 4 receiver for groups, groups.participants — it verifies the signature, answers immediately and does the work after. Install it with composer require slim/slim:^4 slim/psr7 and save it as webhook.php.
<?php
// public/webhook.php - run with: php -S 0.0.0.0:8000 -t public
require __DIR__ . '/../vendor/autoload.php';
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
$secret = getenv('WAPITO_WEBHOOK_SECRET');
$events = ['groups', 'groups.participants'];
/** Checks the X-Wapito-Signature header: t=<ms>,v1=<hex hmac-sha256>. */
function wapito_verify(string $raw, ?string $header, string $secret): bool
{
if (!$header) {
return false;
}
$parts = [];
foreach (explode(',', $header) as $pair) {
[$k, $v] = array_pad(explode('=', $pair, 2), 2, null);
$parts[$k] = $v;
}
if (empty($parts['t']) || empty($parts['v1'])) {
return false;
}
if (abs((int) (microtime(true) * 1000) - (int) $parts['t']) > 300000) {
return false; // 5 minute clock skew
}
$expected = hash_hmac('sha256', $parts['t'] . '.' . $raw, $secret);
return hash_equals($expected, $parts['v1']);
}
$app = AppFactory::create();
$app->post('/wapito', function (Request $request, Response $response) use ($secret, $events) {
$raw = (string) $request->getBody();
if (!wapito_verify($raw, $request->getHeaderLine('X-Wapito-Signature'), $secret)) {
return $response->withStatus(401);
}
$payload = json_decode($raw, true);
if (in_array($payload['event'], $events, true)) {
error_log($payload['event'] . ' ' . json_encode($payload['data']));
}
return $response->withStatus(200); // answer 2xx fast; do the real work in a queue
});
$app->run();
Errors you may hit
Gotchas
- Guzzle throws a ClientException on a 4xx by default, and the useful part - the API error code - is inside $e->getResponse()->getBody(). Either catch it and decode the body, or pass http_errors => false and branch on the status yourself.
- json_decode($raw, true) turns an empty JSON object into an empty PHP array, so is_array() tells you nothing about whether a field arrived. Check array_key_exists() before you read a webhook field.
- PHP running behind Apache or shared hosting often gets a request body that has already been consumed. Read the raw webhook body with file_get_contents("php://input") exactly once and pass it around; a second read returns an empty string and signature checks fail.
- Integer ids overflow on 32-bit builds. WhatsApp group ids and timestamps are strings in Wapito responses - keep them as strings and never cast with (int).
- The default max_execution_time of 30 seconds will kill a bulk loop halfway. Run bulk sends from the CLI SAPI (php send.php), not from a web request.
Where to run it
- Shared hosting or cPanel with a single public webhook.php endpoint
- A VPS managed by Laravel Forge or Ploi, with the sender on a supervisor-managed queue worker
- Google Cloud Run using the official php:8.3-cli image
- Any Heroku-style buildpack platform for the Slim receiver
Pitfalls in PHP
- file_put_contents returns false without throwing when the export directory is not writable under the cron user, so the script exits the group with no transcript saved; test the return value before the leave call.
- A foreach over the decoded list that calls /leave inside the loop keeps paging a list that shrinks as you go; collect the ids first and loop over that array.
Frequently asked questions
Can I rejoin a group I left?
Only with a fresh invite link or by being added by an admin. There is no undo, and the group's history from before you left does not come back with you. If a bot might need to return, keep the group id and make sure a human admin can re-invite it.
Do other members see that I left?
Yes. Departures appear as a system message in the group, the same as joins. If your automation leaves a customer-facing group, consider posting a short handover note first so people know where to direct follow-up questions rather than replying into a thread nobody is watching.
What happens to messages I sent before leaving?
They stay in the group for everyone else, exactly as they were - leaving retracts nothing at all. If a message needs removing, delete it before you leave, and remember that WhatsApp only allows deletion for everyone within a limited window after the message was sent. After that window, and after you have left, the message is simply part of the group history.
Related
Try it on your own number
Create a channel, link a WhatsApp number by QR or pairing code, and call the API in a couple of minutes. The Sandbox plan is free and needs no card.