Build and operate
PHP SDK and Laravel package
paaltech/paalchat-php and paaltech/paalchat-laravel - typed errors, safe retries and verified callbacks.
In a nutshell
composer require paaltech/paalchat-laravel, set PAALCHAT_API_KEY and PAALCHAT_WEBHOOK_SECRET. Writes get an Idempotency-Key automatically and are retried safely; errors are typed exceptions with error codes; callbacks arrive verified and de-duplicated as Laravel events.
Install
composer require paaltech/paalchat-laravel # Laravel (includes the PHP client)
composer require paaltech/paalchat-php # any PHP 8.2+ project
PAALCHAT_API_KEY=sk_live_... # sk_test_... for the sandbox
PAALCHAT_WEBHOOK_SECRET=... # your webhook endpoint's secret
Send
use PaalTech\PaalChat\Laravel\Facades\PaalChat;
PaalChat::businesses()->upsert(['external_id' => 'presec', 'name' => 'Presec Legon']);
PaalChat::messages('presec')->sendTemplate(
'+233241234567', 'fee_reminder', 'en_US',
['parent' => 'Mrs Mensah', 'student' => 'Kofi'],
reference: 'notification-4411',
);
Without Laravel: $paalchat = new PaalTech\PaalChat\PaalChat(getenv('PAALCHAT_API_KEY'));
then the same calls on $paalchat.
| Area | Calls |
|---|---|
| Product | me(), usage([...]) |
| Businesses | businesses()->upsert(), get(), list() |
whatsapp($business)->connectLink(), status(), phones(), disconnect() |
|
| Templates | templates($business)->list(), create(), delete() |
| Messages | messages($business)->send(), sendText(), sendTemplate(), get() |
| Media | media($business)->upload($path), get($id) |
| Contacts | contacts($business)->list(), upsert(), get(), update() |
| Conversations | conversations($business)->list(), get(), update(), messages(), addNote() |
| Inbox | inbox($business)->signIn($staff), members(), removeAccess() |
| Sandbox | sandbox($business)->incoming(), status() |
Retries and errors
- Every write gets an
Idempotency-Key(reused across its retries), so a retry never acts twice. Pass your own as the last argument ofsend()if you retry later yourself. - Network errors,
429(afterRetry-After) and5xxare retried with backoff (max_retries, default 2). - Errors are exceptions:
AuthenticationException(401),PermissionException(403),NotFoundException(404),ConflictException(409),ValidationException(422, with$e->errors),RateLimitException(429,$e->retryAfter),ServerException,ConnectionException. Each has$e->errorCodeand$e->requestId.
use PaalTech\PaalChat\Exceptions\ValidationException;
try {
PaalChat::messages('presec')->sendText('233241234567', 'Hello', reference: 'inbox-991');
} catch (ValidationException $e) {
if ($e->errorCode === 'outside_service_window') {
// offer a template instead
}
}
Callbacks
The Laravel package registers POST /paalchat/callback (set PAALCHAT_WEBHOOK_PATH
to change it; exclude it from CSRF). Each callback is verified on the raw body,
de-duplicated on X-PaalChat-Delivery, answered at once, and fired as
PaalChatEventReceived and paalchat.<event>:
use Illuminate\Support\Facades\Event;
use PaalTech\PaalChat\Event as PaalChatEvent;
Event::listen('paalchat.message.status', function (PaalChatEvent $event) {
Notification::where('id', $event->data['reference'])->first()?->advanceTo($event->data['status']);
});
Without Laravel, verify yourself:
use PaalTech\PaalChat\Webhook;
$event = Webhook::constructEvent(file_get_contents('php://input'), $_SERVER['HTTP_X_PAALCHAT_SIGNATURE'] ?? '', $secret);