A PHP client for the Kudosity API. This monorepo contains two packages:
| Package | For | Repository |
|---|---|---|
expertsystemsau/kudosity-php-client |
Framework-agnostic PHP | expertsystemsau/kudosity-php-client |
expertsystemsau/kudosity-laravel-client |
Laravel (includes the core client) | expertsystemsau/kudosity-laravel-client |
Both are split from this monorepo's packages/ directory on every push to
main and every v* tag — see .github/workflows/split.yml.
This is the 2.x line of the SDK. Kudosity runs two APIs: V1 (
api.transmitsms.com, HTTP Basic auth with an API key and secret) and V2 (api.transmitmessage.com, header auth with the key alone). Every package, class, config key and environment variable is renamed to Kudosity; V1 support ships under the new names, and V2's four messaging channels — single-recipient SMS, MMS, WhatsApp, RCS — are wired onto the client alongside it. API-managed webhooks and senders arrive before this line reaches2.0.0. Upgrading from 1.x? See UPGRADING.md.
Framework-agnostic PHP client for the Kudosity API.
Laravel notification channel integration (includes the core client) — four channels, the V2 webhook receiver route, and kudosity:webhook:* commands.
Install the core client package:
composer require expertsystemsau/kudosity-php-clientInstall the Laravel integration package (includes the core client):
composer require expertsystemsau/kudosity-laravel-clientThen publish the configuration file:
php artisan vendor:publish --tag="kudosity-config"use ExpertSystems\Kudosity\KudosityClient;
$client = new KudosityClient(
apiKey: 'your-api-key',
apiSecret: 'your-api-secret'
);Add your credentials to your .env file:
KUDOSITY_API_KEY=your-api-key
KUDOSITY_API_SECRET=your-api-secret
# Optional default sender ID — see "Sender IDs" below before setting this
KUDOSITY_FROM=The from value (KUDOSITY_FROM, or the per-message from option) is the sender
ID recipients see. It can be:
- A dedicated virtual number (VMN) in international format, e.g.
61491570012— supports two-way messaging (recipients can reply). - An alphanumeric sender ID ("alpha tag") such as
MyBrand— max 11 characters, letters and digits only, no spaces (validated byPhoneNumber::isValidSenderId()). One-way only; recipients cannot reply. - Omitted (leave empty) — Kudosity falls back to a shared number for the destination country.
⚠️ Alpha tags must be registered and approved before you can send with them. For messages to Australian numbers, alphanumeric sender IDs must be listed on the ACMA SMS Sender ID Register (enforced from 1 July 2026) — an unregistered sender ID is replaced with "Unverified" on the recipient's device. Registration requires your registered entity name, ABN, and an authorised contact. Register your sender IDs through the Kudosity dashboard before settingKUDOSITY_FROM; until then, leavefromempty to send from a shared number.
The client is resource-based, and V2 is where new work belongs. Its four
messaging channels — sms(), mms(), whatsapp() and rcs() — each take a
single recipient and send immediately. See
V2 channels in the client
package README for the full method list and the per-endpoint response envelope
table.
use ExpertSystems\Kudosity\KudosityClient;
use ExpertSystems\Kudosity\Enums\WebhookEventType;
$client = new KudosityClient('api-key', 'api-secret');
// One recipient, sent now. message_ref is your correlation key — it comes back
// on every webhook this message produces, and it is the only one V2 offers.
$sms = $client->sms()->send(
'Hello from Kudosity!',
to: '61491570006',
from: '61491570017',
messageRef: 'order-9931:cust-4471',
);
$sms->id; // the UUID webhook events match against
$sms->status; // MessageStatus enum
$client->mms()->send('61491570006', '61491570017', ['https://example.com/product.jpg']);
$client->whatsapp()->text('Hi from WhatsApp!', '61491570010');
$client->rcs()->send('Hi from RCS!', '61491570010', 'DemoSender'); // an AGENT ID, not a number
// Read one back, or list with filters
$client->sms()->get($sms->id);
$client->sms()->list(messageRef: 'order-9931:cust-4471');V2 has no per-send callback URL. Delivery receipts, replies, link hits and
opt-outs all arrive through one account-level webhook, so a send migrated from
bulk() to sms() without registering one
silently stops reporting:
$client->webhooks()->ensure(
name: 'Production events',
url: 'https://your-app.example.com/webhooks/kudosity/v2',
eventTypes: [WebhookEventType::SmsStatus, WebhookEventType::SmsInbound],
);$client->senders() reads sender registrations and runs the SMS verification
flow.
V1 covers everything V2 cannot express — contact lists, multi-recipient sends, scheduling, validity windows, per-send callbacks and reporting — and it is not deprecated. Use it when you need one of those, not by default.
use ExpertSystems\Kudosity\Requests\SendSmsRequest;
// Multiple recipients (comma-separated, up to 500), or a contact list
$sms = $client->bulk()->send('Bulk message', '+61491570006,+61491570007');
$messageId = $sms->messageId;
// Extra options (replies-to-email, callbacks, scheduling, validity) — pass a
// configure closure. Connector defaults still apply, unlike sendRequest().
$client->bulk()->send('Hello!', '+61491570006', configure: fn (SendSmsRequest $r) =>
$r->repliesToEmail('inbox@example.com')->validity(60)
);
// Full control with no connector defaults applied — build a request yourself
$request = (new SendSmsRequest('Scheduled message'))
->to('+61491570006')
->from('MySenderID')
->scheduledAt('2026-12-25 09:00:00');
$client->bulk()->sendRequest($request);
// Reporting and account operations are V1 only
$message = $client->reporting()->getMessage($messageId);
$stats = $client->reporting()->getStats($messageId);
$replies = $client->reporting()->getAllResponses();
$balance = $client->account()->getBalance();List endpoints return a paginator that lazily fetches every page as you iterate.
items() yields the individual records across all pages:
// Iterate every virtual number, page by page
foreach ($client->numbers()->all()->items() as $number) {
echo $number['number'].PHP_EOL;
}
// Works the same for lists, keywords, sent messages, responses, and list members
$client->lists()->all(); // contact lists
$client->keywords()->all(); // keywords
$client->reporting()->getSent($msgId); // recipients of a message
$client->reporting()->getUserSent(); // all messages sent by the account
$client->lists()->getContacts($listId); // members of a list
// Request 50 records per page and cap how many pages are walked
$numbers = $client->numbers()->all()
->setPerPageLimit(50)
->setMaxPages(3)
->collect() // lazy collection of items across the fetched pages
->all();The SDK maps each endpoint's response envelope to the right item key
automatically, so iteration returns the records regardless of which key the API
uses (numbers, lists, recipients, messages, members, responses, …).
The facade proxies to the same resources as the core client.
use ExpertSystems\Kudosity\Laravel\Facades\Kudosity;
// V2 — one recipient, sent now
Kudosity::sms()->send('Hi from Kudosity!', '61491570006', '61491570017');
// V1 — multiple recipients, contact lists and scheduling live here
Kudosity::bulk()->send('Hello from Laravel!', '+61491570006,+61491570007');
// Reporting and account operations are V1 only
$balance = Kudosity::account()->getBalance();Create a notification that uses the Kudosity channel:
use Illuminate\Notifications\Notification;
use ExpertSystems\Kudosity\Laravel\Notifications\KudosityMessage;
class OrderShipped extends Notification
{
public function via($notifiable): array
{
return ['kudosity'];
}
public function toKudosity($notifiable): KudosityMessage
{
return KudosityMessage::create('Your order has been shipped!')
->from('MyStore');
}
}Add the routeNotificationForKudosity method to your notifiable model:
class User extends Authenticatable
{
use Notifiable;
public function routeNotificationForKudosity($notification): ?string
{
return $this->phone_number;
}
}Then send notifications:
$user->notify(new OrderShipped());php artisan kudosity:webhook:syncPut this in your deploy script alongside migrate. It is idempotent — running
it twice registers one webhook, not two — and it repairs drift that a presence
check cannot see, but only some of it in place. Rotating KUDOSITY_SIGNING_KEY
or APP_KEY updates the existing registration and keeps its id: without this,
it keeps receiving deliveries the receiver then rejects with a 403, and nothing
reports that back to you. Changing kudosity.webhooks.prefix or moving
APP_URL is different — the path and host are part of the registration's
identity, so the old one becomes a different endpoint. sync registers a
new webhook for it and leaves the old one running: nothing here deletes, and
the stale registration will not appear in the duplicate warning either, since
that only covers registrations sharing the current identity. Those old
deliveries 404 (the path no longer routes) rather than 403 (a stale signature
on a path that still routes). Run kudosity:webhook:list after either change
and remove the stale row with kudosity:webhook:delete.
kudosity:webhook:install remains the imperative one-shot, for registering an
additional, differently-filtered webhook.
Only permitted environments may run it. Registrations are account-level, so
one made from staging receives production's delivery receipts and inbound
replies — message bodies and phone numbers included. kudosity.webhooks.sync.environments
controls this, defaults to ['production'], fails closed on an empty or
absent list, and has no command-line override. kudosity:webhook:list is
read-only and ungated.
If several of your deployments share one Kudosity account and sender, each
receiver will be delivered events for messages the others sent. Write listeners
that treat an unrecognised message_ref as ordinary rather than an error.
packages/
├── kudosity-client/ # Core PHP client (no framework dependencies)
│ └── src/
│ ├── KudosityClient.php
│ └── Exceptions/
│ └── KudosityException.php
│
└── kudosity-laravel/ # Laravel integration
├── src/
│ ├── KudosityServiceProvider.php
│ ├── Facades/
│ │ └── Kudosity.php
│ └── Notifications/
│ ├── KudosityChannel.php
│ └── KudosityMessage.php
└── config/
└── kudosity.php
This monorepo runs two independent suites.
The Laravel integration suite (Pest 4 + Orchestra Testbench, PHP 8.3+) — 168 tests, 394 assertions:
composer install
composer testThe client package's own suite (PHPUnit 11), installed and run standalone — no Laravel, no Testbench — on the PHP 8.2 floor both packages declare, plus 8.3 and 8.4 — 785 tests, 1470 assertions:
cd packages/kudosity-client
composer install
vendor/bin/phpunitPHP 8.2 itself usually isn't available in a local toolchain; run the same suite in a container instead:
cd packages/kudosity-client
docker run --rm -v "$PWD":/app -w /app php:8.2-cli php vendor/bin/phpunitPlease see CHANGELOG for more information on what has changed recently.
Migrating from 1.x? See UPGRADING.md.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.