PHPackages                             calisero/laravel-sms - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. calisero/laravel-sms

ActiveLibrary

calisero/laravel-sms
====================

Laravel package for sending SMS through Calisero API

1.1.2(6mo ago)1203↓50%MITPHPPHP ^8.2 || ^8.4CI passing

Since Sep 24Pushed 6mo agoCompare

[ Source](https://github.com/calisero/laravel-sms)[ Packagist](https://packagist.org/packages/calisero/laravel-sms)[ RSS](/packages/calisero-laravel-sms/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (8)Dependencies (11)Versions (11)Used By (0)

Laravel SMS Package for Calisero
================================

[](#laravel-sms-package-for-calisero)

[![Latest Version on Packagist](https://camo.githubusercontent.com/950fe4f7e32275ed3e9feeca51694020cf0314551eed3d20d5c600c21dbf4000/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f63616c697365726f2f6c61726176656c2d736d732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/calisero/laravel-sms)[![tests](https://github.com/calisero/laravel-sms/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/calisero/laravel-sms/actions/workflows/ci.yml)[![PHPStan](https://camo.githubusercontent.com/fa7d257d0c5c1cf237ac3490ef3a5561626b17fcb0a8547c01b0bb8746554e60/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230392d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](https://phpstan.org)[![License](https://camo.githubusercontent.com/321f904ccc42669f0d26607b89206f8f22ce4e886c1929b33b758829e09278b2/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f63616c697365726f2f6c61726176656c2d736d732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/calisero/laravel-sms)[![Tests](https://camo.githubusercontent.com/941b20f1ffce58e3cc2406f58b5ae1622c512a125e6f9c216adf76d9be9bf1ac/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f63616c697365726f2f6c61726176656c2d736d732f63692e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/calisero/laravel-sms/actions/workflows/ci.yml)[![Total Downloads](https://camo.githubusercontent.com/0bf08d45bf17efb8111ab2fb08a8f3c60c1d77e9f6b9ba8a472a9ba0dc02ecfa/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f63616c697365726f2f6c61726176656c2d736d732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/calisero/laravel-sms)

A first-class Laravel 12 package that wraps the [Calisero PHP SDK](https://github.com/calisero/calisero-php) and provides idiomatic Laravel features for sending SMS messages through the Calisero API.

Features
--------

[](#features)

- 🚀 **Laravel 12** ready with full support for the latest features
- 📱 **Easy SMS sending** via Facade, Notification channels, or direct client usage
- 🔐 **Two-Factor Authentication** with verification codes API
- 🔒 **Webhook handling** with token-based security
- ✅ **Validation rules** for phone numbers (E.164) and sender IDs
- 🎯 **Queue support** for reliable message delivery
- 🧪 **Artisan commands** for testing and development
- 🏗️ **PSR-4 compliant** with full test coverage

> Internal package logging was removed. Add your own logging in event listeners/subscribers.

Installation
------------

[](#installation)

You can install the package via Composer:

```
composer require calisero/laravel-sms
```

### Publish the configuration file

[](#publish-the-configuration-file)

```
php artisan vendor:publish --provider="Calisero\\LaravelSms\\ServiceProvider" --tag="calisero-config"
```

### Configure your environment

[](#configure-your-environment)

Add your Calisero API credentials to your `.env` file:

```
# Required: API Configuration
CALISERO_API_KEY=your-api-key-here
CALISERO_BASE_URI=https://rest.calisero.ro/api/v1

# Optional: Account ID for balance queries
CALISERO_ACCOUNT_ID=your-account-id

# Optional: Connection Settings
CALISERO_TIMEOUT=10.0
CALISERO_CONNECT_TIMEOUT=3.0
CALISERO_RETRIES=5
CALISERO_RETRY_BACKOFF_MS=200

# Optional: Webhook Configuration
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=your-shared-secret

# Optional: Credit Monitoring
CALISERO_CREDIT_LOW=500
CALISERO_CREDIT_CRITICAL=100
```

Usage
-----

[](#usage)

### Quick Start

[](#quick-start)

#### Sending SMS via Facade

[](#sending-sms-via-facade)

```
use Calisero\LaravelSms\Facades\Calisero;

$response = Calisero::sendSms([
    'to' => '+40712345678',
    'text' => 'Hello from Laravel!',
    // 'from' => 'MyBrand' // Include ONLY if approved by Calisero
]);
```

#### Verification Codes (2FA)

[](#verification-codes-2fa)

Send and verify one-time codes for two-factor authentication:

```
use Calisero\LaravelSms\Facades\Calisero;

// Send a verification code (with brand)
$response = Calisero::sendVerification([
    'to' => '+40712345678',
    'brand' => 'MyApp', // Required if no template
    'expires_in' => 5, // Optional: 1-10 minutes, default 5
]);

// OR send with custom template
$response = Calisero::sendVerification([
    'to' => '+40712345678',
    'template' => 'Your verification code is {code}. Valid for 5 minutes.',
    'expires_in' => 5,
]);

// The response includes expiration time
echo "Code expires at: " . $response->expires_at;

// Check/verify the code entered by user
$result = Calisero::checkVerification([
    'to' => '+40712345678',
    'code' => '123456', // Code entered by user (6 characters)
]);

if ('verified' === $result->status) {
    // Code is valid, proceed with authentication
    echo "Verification successful!";
} else {
    // Code is invalid or expired
    echo "Verification failed!";
}
```

> **Note**: Either `brand` OR `template` is required when sending verification codes. The template must contain `{code}` placeholder. Codes are 6 characters and sent via SMS.

### Using Notifications

[](#using-notifications)

Create a notification class:

```
use Calisero\LaravelSms\Notification\SmsMessage;
use Illuminate\Notifications\Notification;

class WelcomeNotification extends Notification
{
    public function via($notifiable): array
    {
        return ['calisero'];
    }

    public function toCalisero($notifiable): SmsMessage
    {
        return SmsMessage::create('Welcome to our app!')
            // ->from('MyBrand') // Uncomment only after approval
            ;
    }
}
```

Send the notification:

```
use App\Models\User;
use App\Notifications\WelcomeNotification;

$user = User::find(1);
$user->notify(new WelcomeNotification());
```

For the notification to work, your User model should implement the `routeNotificationForCalisero` method:

```
public function routeNotificationForCalisero($notification): string
{
    return $this->phone; // Return the user's phone number
}
```

### Using the Direct Client

[](#using-the-direct-client)

For more control, inject the client directly:

```
use Calisero\LaravelSms\Contracts\SmsClient;

class SmsService
{
    public function __construct(private SmsClient $client) {}

    public function sendWelcomeSms(string $phone): void
    {
        $this->client->sendSms([
            'to' => $phone,
            'text' => 'Welcome to our service!',
            'from' => 'MyApp',
            'idempotencyKey' => 'welcome-' . uniqid(),
        ]);
    }
}
```

### Validation Rules

[](#validation-rules)

Use the included validation rules in your form requests:

```
use Calisero\LaravelSms\Validation\Rule;
use Illuminate\Foundation\Http\FormRequest;

class SendSmsRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'phone' => ['required', Rule::phoneE164()],
            'sender_id' => ['required', Rule::senderId()],
            'message' => ['required', 'string', 'max:1600'],
        ];
    }
}
```

### Webhook Handling

[](#webhook-handling)

Enable webhooks by setting `CALISERO_WEBHOOK_ENABLED=true`. The package will register a POST endpoint at `/calisero/webhook` (or your configured `CALISERO_WEBHOOK_PATH`).

#### Securing the Webhook (Query Token)

[](#securing-the-webhook-query-token)

If you also set `CALISERO_WEBHOOK_TOKEN=your-shared-secret`, the package will:

- Automatically append `?token=your-shared-secret` to the injected `callback_url` sent to Calisero (only when you did not supply a custom `callback_url`).
- Register a middleware that rejects any incoming webhook request not containing the correct `token` query parameter.

Requirements when token security is enabled:

- Each webhook request from Calisero must include the query parameter `token` with the exact configured value.
- If you manually override `callback_url`, you are responsible for including the `?token=...` segment yourself.
- If your explicit URL already contains a `token=` parameter, the library will not modify it.

Environment example:

```
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=super-secret-value
```

Example of explicit override (token already present, no modification by the library):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom secured callback',
    'callback_url' => 'https://example.com/custom-hook?token=' . urlencode(config('calisero.webhook.token')),
]);
```

Rotation tip: rotate the token by

1. Adding a temporary second endpoint (optional) or a maintenance window.
2. Updating `CALISERO_WEBHOOK_TOKEN`.
3. Redeploying and updating the callback URL in Calisero (or sending a new message to propagate the injected URL).

If you leave `CALISERO_WEBHOOK_TOKEN` empty, no token middleware is attached and the endpoint is publicly accessible (POST only). Consider other controls (IP allow-list, WAF) if you opt out of the token.

Listen for the events:

```
Event::listen(MessageSent::class, fn (MessageSent $e) => ...);
Event::listen(MessageDelivered::class, fn (MessageDelivered $e) => ...);
Event::listen(MessageFailed::class, fn (MessageFailed $e) => ...);
```

Statuses currently emitted (lifecycle):

- `sent` – the message was accepted and dispatched to the network
- `delivered` – the handset/network confirmed delivery
- `failed` – delivery permanently failed

Webhook payload example (flat structure):

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "sent",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": null,
  "remainingBalance": 999.43
}
```

When the same message is later delivered you will receive another webhook with:

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "delivered",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": "2025-09-19T12:00:24.000000Z",
  "remainingBalance": 999.43
}
```

A failed attempt would have `"status": "failed"` and usually a `deliveredAt` of `null`.

#### Automatic callback\_url Injection

[](#automatic-callback_url-injection)

If `CALISERO_WEBHOOK_ENABLED=true`, every `sendSms()` call **without** an explicit `callback_url` (or `callbackUrl`) automatically includes one pointing to the named route `calisero.webhook` (if registered) or a URL built from `app.url` + the configured path.
To override, supply your own `callback_url` parameter.
To disable injection, set `CALISERO_WEBHOOK_ENABLED=false` or omit the env variable.

Edge cases:

- If `app.url` is not set and the route helper fails, a root-relative path like `/calisero/webhook` is used.
- Passing either `callback_url` or `callbackUrl` prevents injection.

Example (override):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom callback',
    'callback_url' => 'https://example.com/custom-hook',
]);
```

### Artisan Commands

[](#artisan-commands)

The package provides several Artisan commands for testing and development:

#### Test SMS Sending

[](#test-sms-sending)

```
php artisan calisero:sms:test +40712345678 --from=YourApp --text="Test message"
```

#### SMS Status

[](#sms-status)

```
php artisan calisero:sms:status 019961d8-3338-700c-be17-10d061f03a5c
```

#### Verification Commands

[](#verification-commands)

Send a verification code with brand:

```
php artisan calisero:verification:send +40712345678 --brand=MyApp
```

Send a verification code with custom template:

```
php artisan calisero:verification:send +40712345678 --template="Your code is {code}" --expires-in=5
```

Check a verification code:

```
php artisan calisero:verification:check +40712345678 123456
```

#### Webhook Verification

[](#webhook-verification)

Enable webhook handling by setting `CALISERO_WEBHOOK_ENABLED=true`. The package will register a POST endpoint at `/calisero/webhook` (or your configured `CALISERO_WEBHOOK_PATH`).

If you also set `CALISERO_WEBHOOK_TOKEN=your-shared-secret`, the package will:

- Automatically append `?token=your-shared-secret` to the injected `callback_url` sent to Calisero (only when you did not supply a custom `callback_url`).
- Register a middleware that rejects any incoming webhook request not containing the correct `token` query parameter.

Requirements when token security is enabled:

- Each webhook request from Calisero must include the query parameter `token` with the exact configured value.
- If you manually override `callback_url`, you are responsible for including the `?token=...` segment yourself.
- If your explicit URL already contains a `token=` parameter, the library will not modify it.

Environment example:

```
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=super-secret-value
```

Example of explicit override (token already present, no modification by the library):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom secured callback',
    'callback_url' => 'https://example.com/custom-hook?token=' . urlencode(config('calisero.webhook.token')),
]);
```

Rotation tip: rotate the token by

1. Adding a temporary second endpoint (optional) or a maintenance window.
2. Updating `CALISERO_WEBHOOK_TOKEN`.
3. Redeploying and updating the callback URL in Calisero (or sending a new message to propagate the injected URL).

If you leave `CALISERO_WEBHOOK_TOKEN` empty, no token middleware is attached and the endpoint is publicly accessible (POST only). Consider other controls (IP allow-list, WAF) if you opt out of the token.

Listen for the events:

```
Event::listen(MessageSent::class, fn (MessageSent $e) => ...);
Event::listen(MessageDelivered::class, fn (MessageDelivered $e) => ...);
Event::listen(MessageFailed::class, fn (MessageFailed $e) => ...);
```

Statuses currently emitted (lifecycle):

- `sent` – the message was accepted and dispatched to the network
- `delivered` – the handset/network confirmed delivery
- `failed` – delivery permanently failed

Webhook payload example (flat structure):

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "sent",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": null,
  "remainingBalance": 999.43
}
```

When the same message is later delivered you will receive another webhook with:

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "delivered",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": "2025-09-19T12:00:24.000000Z",
  "remainingBalance": 999.43
}
```

A failed attempt would have `"status": "failed"` and usually a `deliveredAt` of `null`.

#### Automatic callback\_url Injection

[](#automatic-callback_url-injection-1)

If `CALISERO_WEBHOOK_ENABLED=true`, every `sendSms()` call **without** an explicit `callback_url` (or `callbackUrl`) automatically includes one pointing to the named route `calisero.webhook` (if registered) or a URL built from `app.url` + the configured path.
To override, supply your own `callback_url` parameter.
To disable injection, set `CALISERO_WEBHOOK_ENABLED=false` or omit the env variable.

Edge cases:

- If `app.url` is not set and the route helper fails, a root-relative path like `/calisero/webhook` is used.
- Passing either `callback_url` or `callbackUrl` prevents injection.

Example (override):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom callback',
    'callback_url' => 'https://example.com/custom-hook',
]);
```

### Artisan Commands

[](#artisan-commands-1)

The package provides several Artisan commands for testing and development:

#### Test SMS Sending

[](#test-sms-sending-1)

```
php artisan calisero:sms:test +40712345678 --from=YourApp --text="Test message"
```

#### SMS Status

[](#sms-status-1)

```
php artisan calisero:sms:status 019961d8-3338-700c-be17-10d061f03a5c
```

#### Verification Commands

[](#verification-commands-1)

Send a verification code with brand:

```
php artisan calisero:verification:send +40712345678 --brand=MyApp
```

Send a verification code with custom template:

```
php artisan calisero:verification:send +40712345678 --template="Your code is {code}" --expires-in=5
```

Check a verification code:

```
php artisan calisero:verification:check +40712345678 123456
```

#### Webhook Verification

[](#webhook-verification-1)

Enable webhook handling by setting `CALISERO_WEBHOOK_ENABLED=true`. The package will register a POST endpoint at `/calisero/webhook` (or your configured `CALISERO_WEBHOOK_PATH`).

If you also set `CALISERO_WEBHOOK_TOKEN=your-shared-secret`, the package will:

- Automatically append `?token=your-shared-secret` to the injected `callback_url` sent to Calisero (only when you did not supply a custom `callback_url`).
- Register a middleware that rejects any incoming webhook request not containing the correct `token` query parameter.

Requirements when token security is enabled:

- Each webhook request from Calisero must include the query parameter `token` with the exact configured value.
- If you manually override `callback_url`, you are responsible for including the `?token=...` segment yourself.
- If your explicit URL already contains a `token=` parameter, the library will not modify it.

Environment example:

```
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=super-secret-value
```

Example of explicit override (token already present, no modification by the library):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom secured callback',
    'callback_url' => 'https://example.com/custom-hook?token=' . urlencode(config('calisero.webhook.token')),
]);
```

Rotation tip: rotate the token by

1. Adding a temporary second endpoint (optional) or a maintenance window.
2. Updating `CALISERO_WEBHOOK_TOKEN`.
3. Redeploying and updating the callback URL in Calisero (or sending a new message to propagate the injected URL).

If you leave `CALISERO_WEBHOOK_TOKEN` empty, no token middleware is attached and the endpoint is publicly accessible (POST only). Consider other controls (IP allow-list, WAF) if you opt out of the token.

Listen for the events:

```
Event::listen(MessageSent::class, fn (MessageSent $e) => ...);
Event::listen(MessageDelivered::class, fn (MessageDelivered $e) => ...);
Event::listen(MessageFailed::class, fn (MessageFailed $e) => ...);
```

Statuses currently emitted (lifecycle):

- `sent` – the message was accepted and dispatched to the network
- `delivered` – the handset/network confirmed delivery
- `failed` – delivery permanently failed

Webhook payload example (flat structure):

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "sent",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": null,
  "remainingBalance": 999.43
}
```

When the same message is later delivered you will receive another webhook with:

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "delivered",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": "2025-09-19T12:00:24.000000Z",
  "remainingBalance": 999.43
}
```

A failed attempt would have `"status": "failed"` and usually a `deliveredAt` of `null`.

#### Automatic callback\_url Injection

[](#automatic-callback_url-injection-2)

If `CALISERO_WEBHOOK_ENABLED=true`, every `sendSms()` call **without** an explicit `callback_url` (or `callbackUrl`) automatically includes one pointing to the named route `calisero.webhook` (if registered) or a URL built from `app.url` + the configured path.
To override, supply your own `callback_url` parameter.
To disable injection, set `CALISERO_WEBHOOK_ENABLED=false` or omit the env variable.

Edge cases:

- If `app.url` is not set and the route helper fails, a root-relative path like `/calisero/webhook` is used.
- Passing either `callback_url` or `callbackUrl` prevents injection.

Example (override):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom callback',
    'callback_url' => 'https://example.com/custom-hook',
]);
```

### Artisan Commands

[](#artisan-commands-2)

The package provides several Artisan commands for testing and development:

#### Test SMS Sending

[](#test-sms-sending-2)

```
php artisan calisero:sms:test +40712345678 --from=YourApp --text="Test message"
```

#### SMS Status

[](#sms-status-2)

```
php artisan calisero:sms:status 019961d8-3338-700c-be17-10d061f03a5c
```

#### Verification Commands

[](#verification-commands-2)

Send a verification code with brand:

```
php artisan calisero:verification:send +40712345678 --brand=MyApp
```

Send a verification code with custom template:

```
php artisan calisero:verification:send +40712345678 --template="Your code is {code}" --expires-in=5
```

Check a verification code:

```
php artisan calisero:verification:check +40712345678 123456
```

#### Webhook Verification

[](#webhook-verification-2)

Enable webhook handling by setting `CALISERO_WEBHOOK_ENABLED=true`. The package will register a POST endpoint at `/calisero/webhook` (or your configured `CALISERO_WEBHOOK_PATH`).

If you also set `CALISERO_WEBHOOK_TOKEN=your-shared-secret`, the package will:

- Automatically append `?token=your-shared-secret` to the injected `callback_url` sent to Calisero (only when you did not supply a custom `callback_url`).
- Register a middleware that rejects any incoming webhook request not containing the correct `token` query parameter.

Requirements when token security is enabled:

- Each webhook request from Calisero must include the query parameter `token` with the exact configured value.
- If you manually override `callback_url`, you are responsible for including the `?token=...` segment yourself.
- If your explicit URL already contains a `token=` parameter, the library will not modify it.

Environment example:

```
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=super-secret-value
```

Example of explicit override (token already present, no modification by the library):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom secured callback',
    'callback_url' => 'https://example.com/custom-hook?token=' . urlencode(config('calisero.webhook.token')),
]);
```

Rotation tip: rotate the token by

1. Adding a temporary second endpoint (optional) or a maintenance window.
2. Updating `CALISERO_WEBHOOK_TOKEN`.
3. Redeploying and updating the callback URL in Calisero (or sending a new message to propagate the injected URL).

If you leave `CALISERO_WEBHOOK_TOKEN` empty, no token middleware is attached and the endpoint is publicly accessible (POST only). Consider other controls (IP allow-list, WAF) if you opt out of the token.

Listen for the events:

```
Event::listen(MessageSent::class, fn (MessageSent $e) => ...);
Event::listen(MessageDelivered::class, fn (MessageDelivered $e) => ...);
Event::listen(MessageFailed::class, fn (MessageFailed $e) => ...);
```

Statuses currently emitted (lifecycle):

- `sent` – the message was accepted and dispatched to the network
- `delivered` – the handset/network confirmed delivery
- `failed` – delivery permanently failed

Webhook payload example (flat structure):

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "sent",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": null,
  "remainingBalance": 999.43
}
```

When the same message is later delivered you will receive another webhook with:

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "delivered",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": "2025-09-19T12:00:24.000000Z",
  "remainingBalance": 999.43
}
```

A failed attempt would have `"status": "failed"` and usually a `deliveredAt` of `null`.

#### Automatic callback\_url Injection

[](#automatic-callback_url-injection-3)

If `CALISERO_WEBHOOK_ENABLED=true`, every `sendSms()` call **without** an explicit `callback_url` (or `callbackUrl`) automatically includes one pointing to the named route `calisero.webhook` (if registered) or a URL built from `app.url` + the configured path.
To override, supply your own `callback_url` parameter.
To disable injection, set `CALISERO_WEBHOOK_ENABLED=false` or omit the env variable.

Edge cases:

- If `app.url` is not set and the route helper fails, a root-relative path like `/calisero/webhook` is used.
- Passing either `callback_url` or `callbackUrl` prevents injection.

Example (override):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom callback',
    'callback_url' => 'https://example.com/custom-hook',
]);
```

### Artisan Commands

[](#artisan-commands-3)

The package provides several Artisan commands for testing and development:

#### Test SMS Sending

[](#test-sms-sending-3)

```
php artisan calisero:sms:test +40712345678 --from=YourApp --text="Test message"
```

#### SMS Status

[](#sms-status-3)

```
php artisan calisero:sms:status 019961d8-3338-700c-be17-10d061f03a5c
```

#### Verification Commands

[](#verification-commands-3)

Send a verification code with brand:

```
php artisan calisero:verification:send +40712345678 --brand=MyApp
```

Send a verification code with custom template:

```
php artisan calisero:verification:send +40712345678 --template="Your code is {code}" --expires-in=5
```

Check a verification code:

```
php artisan calisero:verification:check +40712345678 123456
```

#### Webhook Verification

[](#webhook-verification-3)

Enable webhook handling by setting `CALISERO_WEBHOOK_ENABLED=true`. The package will register a POST endpoint at `/calisero/webhook` (or your configured `CALISERO_WEBHOOK_PATH`).

If you also set `CALISERO_WEBHOOK_TOKEN=your-shared-secret`, the package will:

- Automatically append `?token=your-shared-secret` to the injected `callback_url` sent to Calisero (only when you did not supply a custom `callback_url`).
- Register a middleware that rejects any incoming webhook request not containing the correct `token` query parameter.

Requirements when token security is enabled:

- Each webhook request from Calisero must include the query parameter `token` with the exact configured value.
- If you manually override `callback_url`, you are responsible for including the `?token=...` segment yourself.
- If your explicit URL already contains a `token=` parameter, the library will not modify it.

Environment example:

```
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=super-secret-value
```

Example of explicit override (token already present, no modification by the library):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom secured callback',
    'callback_url' => 'https://example.com/custom-hook?token=' . urlencode(config('calisero.webhook.token')),
]);
```

Rotation tip: rotate the token by

1. Adding a temporary second endpoint (optional) or a maintenance window.
2. Updating `CALISERO_WEBHOOK_TOKEN`.
3. Redeploying and updating the callback URL in Calisero (or sending a new message to propagate the injected URL).

If you leave `CALISERO_WEBHOOK_TOKEN` empty, no token middleware is attached and the endpoint is publicly accessible (POST only). Consider other controls (IP allow-list, WAF) if you opt out of the token.

Listen for the events:

```
Event::listen(MessageSent::class, fn (MessageSent $e) => ...);
Event::listen(MessageDelivered::class, fn (MessageDelivered $e) => ...);
Event::listen(MessageFailed::class, fn (MessageFailed $e) => ...);
```

Statuses currently emitted (lifecycle):

- `sent` – the message was accepted and dispatched to the network
- `delivered` – the handset/network confirmed delivery
- `failed` – delivery permanently failed

Webhook payload example (flat structure):

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "sent",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": null,
  "remainingBalance": 999.43
}
```

When the same message is later delivered you will receive another webhook with:

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "delivered",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": "2025-09-19T12:00:24.000000Z",
  "remainingBalance": 999.43
}
```

A failed attempt would have `"status": "failed"` and usually a `deliveredAt` of `null`.

#### Automatic callback\_url Injection

[](#automatic-callback_url-injection-4)

If `CALISERO_WEBHOOK_ENABLED=true`, every `sendSms()` call **without** an explicit `callback_url` (or `callbackUrl`) automatically includes one pointing to the named route `calisero.webhook` (if registered) or a URL built from `app.url` + the configured path.
To override, supply your own `callback_url` parameter.
To disable injection, set `CALISERO_WEBHOOK_ENABLED=false` or omit the env variable.

Edge cases:

- If `app.url` is not set and the route helper fails, a root-relative path like `/calisero/webhook` is used.
- Passing either `callback_url` or `callbackUrl` prevents injection.

Example (override):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom callback',
    'callback_url' => 'https://example.com/custom-hook',
]);
```

### Artisan Commands

[](#artisan-commands-4)

The package provides several Artisan commands for testing and development:

#### Test SMS Sending

[](#test-sms-sending-4)

```
php artisan calisero:sms:test +40712345678 --from=YourApp --text="Test message"
```

#### SMS Status

[](#sms-status-4)

```
php artisan calisero:sms:status 019961d8-3338-700c-be17-10d061f03a5c
```

#### Verification Commands

[](#verification-commands-4)

Send a verification code with brand:

```
php artisan calisero:verification:send +40712345678 --brand=MyApp
```

Send a verification code with custom template:

```
php artisan calisero:verification:send +40712345678 --template="Your code is {code}" --expires-in=5
```

Check a verification code:

```
php artisan calisero:verification:check +40712345678 123456
```

#### Webhook Verification

[](#webhook-verification-4)

Enable webhook handling by setting `CALISERO_WEBHOOK_ENABLED=true`. The package will register a POST endpoint at `/calisero/webhook` (or your configured `CALISERO_WEBHOOK_PATH`).

If you also set `CALISERO_WEBHOOK_TOKEN=your-shared-secret`, the package will:

- Automatically append `?token=your-shared-secret` to the injected `callback_url` sent to Calisero (only when you did not supply a custom `callback_url`).
- Register a middleware that rejects any incoming webhook request not containing the correct `token` query parameter.

Requirements when token security is enabled:

- Each webhook request from Calisero must include the query parameter `token` with the exact configured value.
- If you manually override `callback_url`, you are responsible for including the `?token=...` segment yourself.
- If your explicit URL already contains a `token=` parameter, the library will not modify it.

Environment example:

```
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=super-secret-value
```

Example of explicit override (token already present, no modification by the library):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom secured callback',
    'callback_url' => 'https://example.com/custom-hook?token=' . urlencode(config('calisero.webhook.token')),
]);
```

Rotation tip: rotate the token by

1. Adding a temporary second endpoint (optional) or a maintenance window.
2. Updating `CALISERO_WEBHOOK_TOKEN`.
3. Redeploying and updating the callback URL in Calisero (or sending a new message to propagate the injected URL).

If you leave `CALISERO_WEBHOOK_TOKEN` empty, no token middleware is attached and the endpoint is publicly accessible (POST only). Consider other controls (IP allow-list, WAF) if you opt out of the token.

Listen for the events:

```
Event::listen(MessageSent::class, fn (MessageSent $e) => ...);
Event::listen(MessageDelivered::class, fn (MessageDelivered $e) => ...);
Event::listen(MessageFailed::class, fn (MessageFailed $e) => ...);
```

Statuses currently emitted (lifecycle):

- `sent` – the message was accepted and dispatched to the network
- `delivered` – the handset/network confirmed delivery
- `failed` – delivery permanently failed

Webhook payload example (flat structure):

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "sent",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": null,
  "remainingBalance": 999.43
}
```

When the same message is later delivered you will receive another webhook with:

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "delivered",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": "2025-09-19T12:00:24.000000Z",
  "remainingBalance": 999.43
}
```

A failed attempt would have `"status": "failed"` and usually a `deliveredAt` of `null`.

#### Automatic callback\_url Injection

[](#automatic-callback_url-injection-5)

If `CALISERO_WEBHOOK_ENABLED=true`, every `sendSms()` call **without** an explicit `callback_url` (or `callbackUrl`) automatically includes one pointing to the named route `calisero.webhook` (if registered) or a URL built from `app.url` + the configured path.
To override, supply your own `callback_url` parameter.
To disable injection, set `CALISERO_WEBHOOK_ENABLED=false` or omit the env variable.

Edge cases:

- If `app.url` is not set and the route helper fails, a root-relative path like `/calisero/webhook` is used.
- Passing either `callback_url` or `callbackUrl` prevents injection.

Example (override):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom callback',
    'callback_url' => 'https://example.com/custom-hook',
]);
```

### Artisan Commands

[](#artisan-commands-5)

The package provides several Artisan commands for testing and development:

#### Test SMS Sending

[](#test-sms-sending-5)

```
php artisan calisero:sms:test +40712345678 --from=YourApp --text="Test message"
```

#### SMS Status

[](#sms-status-5)

```
php artisan calisero:sms:status 019961d8-3338-700c-be17-10d061f03a5c
```

#### Verification Commands

[](#verification-commands-5)

Send a verification code with brand:

```
php artisan calisero:verification:send +40712345678 --brand=MyApp
```

Send a verification code with custom template:

```
php artisan calisero:verification:send +40712345678 --template="Your code is {code}" --expires-in=5
```

Check a verification code:

```
php artisan calisero:verification:check +40712345678 123456
```

#### Webhook Verification

[](#webhook-verification-5)

Enable webhook handling by setting `CALISERO_WEBHOOK_ENABLED=true`. The package will register a POST endpoint at `/calisero/webhook` (or your configured `CALISERO_WEBHOOK_PATH`).

If you also set `CALISERO_WEBHOOK_TOKEN=your-shared-secret`, the package will:

- Automatically append `?token=your-shared-secret` to the injected `callback_url` sent to Calisero (only when you did not supply a custom `callback_url`).
- Register a middleware that rejects any incoming webhook request not containing the correct `token` query parameter.

Requirements when token security is enabled:

- Each webhook request from Calisero must include the query parameter `token` with the exact configured value.
- If you manually override `callback_url`, you are responsible for including the `?token=...` segment yourself.
- If your explicit URL already contains a `token=` parameter, the library will not modify it.

Environment example:

```
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=super-secret-value
```

Example of explicit override (token already present, no modification by the library):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom secured callback',
    'callback_url' => 'https://example.com/custom-hook?token=' . urlencode(config('calisero.webhook.token')),
]);
```

Rotation tip: rotate the token by

1. Adding a temporary second endpoint (optional) or a maintenance window.
2. Updating `CALISERO_WEBHOOK_TOKEN`.
3. Redeploying and updating the callback URL in Calisero (or sending a new message to propagate the injected URL).

If you leave `CALISERO_WEBHOOK_TOKEN` empty, no token middleware is attached and the endpoint is publicly accessible (POST only). Consider other controls (IP allow-list, WAF) if you opt out of the token.

Listen for the events:

```
Event::listen(MessageSent::class, fn (MessageSent $e) => ...);
Event::listen(MessageDelivered::class, fn (MessageDelivered $e) => ...);
Event::listen(MessageFailed::class, fn (MessageFailed $e) => ...);
```

Statuses currently emitted (lifecycle):

- `sent` – the message was accepted and dispatched to the network
- `delivered` – the handset/network confirmed delivery
- `failed` – delivery permanently failed

Webhook payload example (flat structure):

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "sent",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": null,
  "remainingBalance": 999.43
}
```

When the same message is later delivered you will receive another webhook with:

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "delivered",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": "2025-09-19T12:00:24.000000Z",
  "remainingBalance": 999.43
}
```

A failed attempt would have `"status": "failed"` and usually a `deliveredAt` of `null`.

#### Automatic callback\_url Injection

[](#automatic-callback_url-injection-6)

If `CALISERO_WEBHOOK_ENABLED=true`, every `sendSms()` call **without** an explicit `callback_url` (or `callbackUrl`) automatically includes one pointing to the named route `calisero.webhook` (if registered) or a URL built from `app.url` + the configured path.
To override, supply your own `callback_url` parameter.
To disable injection, set `CALISERO_WEBHOOK_ENABLED=false` or omit the env variable.

Edge cases:

- If `app.url` is not set and the route helper fails, a root-relative path like `/calisero/webhook` is used.
- Passing either `callback_url` or `callbackUrl` prevents injection.

Example (override):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom callback',
    'callback_url' => 'https://example.com/custom-hook',
]);
```

### Artisan Commands

[](#artisan-commands-6)

The package provides several Artisan commands for testing and development:

#### Test SMS Sending

[](#test-sms-sending-6)

```
php artisan calisero:sms:test +40712345678 --from=YourApp --text="Test message"
```

#### SMS Status

[](#sms-status-6)

```
php artisan calisero:sms:status 019961d8-3338-700c-be17-10d061f03a5c
```

#### Verification Commands

[](#verification-commands-6)

Send a verification code with brand:

```
php artisan calisero:verification:send +40712345678 --brand=MyApp
```

Send a verification code with custom template:

```
php artisan calisero:verification:send +40712345678 --template="Your code is {code}" --expires-in=5
```

Check a verification code:

```
php artisan calisero:verification:check +40712345678 123456
```

#### Webhook Verification

[](#webhook-verification-6)

Enable webhook handling by setting `CALISERO_WEBHOOK_ENABLED=true`. The package will register a POST endpoint at `/calisero/webhook` (or your configured `CALISERO_WEBHOOK_PATH`).

If you also set `CALISERO_WEBHOOK_TOKEN=your-shared-secret`, the package will:

- Automatically append `?token=your-shared-secret` to the injected `callback_url` sent to Calisero (only when you did not supply a custom `callback_url`).
- Register a middleware that rejects any incoming webhook request not containing the correct `token` query parameter.

Requirements when token security is enabled:

- Each webhook request from Calisero must include the query parameter `token` with the exact configured value.
- If you manually override `callback_url`, you are responsible for including the `?token=...` segment yourself.
- If your explicit URL already contains a `token=` parameter, the library will not modify it.

Environment example:

```
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=super-secret-value
```

Example of explicit override (token already present, no modification by the library):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom secured callback',
    'callback_url' => 'https://example.com/custom-hook?token=' . urlencode(config('calisero.webhook.token')),
]);
```

Rotation tip: rotate the token by

1. Adding a temporary second endpoint (optional) or a maintenance window.
2. Updating `CALISERO_WEBHOOK_TOKEN`.
3. Redeploying and updating the callback URL in Calisero (or sending a new message to propagate the injected URL).

If you leave `CALISERO_WEBHOOK_TOKEN` empty, no token middleware is attached and the endpoint is publicly accessible (POST only). Consider other controls (IP allow-list, WAF) if you opt out of the token.

Listen for the events:

```
Event::listen(MessageSent::class, fn (MessageSent $e) => ...);
Event::listen(MessageDelivered::class, fn (MessageDelivered $e) => ...);
Event::listen(MessageFailed::class, fn (MessageFailed $e) => ...);
```

Statuses currently emitted (lifecycle):

- `sent` – the message was accepted and dispatched to the network
- `delivered` – the handset/network confirmed delivery
- `failed` – delivery permanently failed

Webhook payload example (flat structure):

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "sent",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": null,
  "remainingBalance": 999.43
}
```

When the same message is later delivered you will receive another webhook with:

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "delivered",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": "2025-09-19T12:00:24.000000Z",
  "remainingBalance": 999.43
}
```

A failed attempt would have `"status": "failed"` and usually a `deliveredAt` of `null`.

#### Automatic callback\_url Injection

[](#automatic-callback_url-injection-7)

If `CALISERO_WEBHOOK_ENABLED=true`, every `sendSms()` call **without** an explicit `callback_url` (or `callbackUrl`) automatically includes one pointing to the named route `calisero.webhook` (if registered) or a URL built from `app.url` + the configured path.
To override, supply your own `callback_url` parameter.
To disable injection, set `CALISERO_WEBHOOK_ENABLED=false` or omit the env variable.

Edge cases:

- If `app.url` is not set and the route helper fails, a root-relative path like `/calisero/webhook` is used.
- Passing either `callback_url` or `callbackUrl` prevents injection.

Example (override):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom callback',
    'callback_url' => 'https://example.com/custom-hook',
]);
```

### Artisan Commands

[](#artisan-commands-7)

The package provides several Artisan commands for testing and development:

#### Test SMS Sending

[](#test-sms-sending-7)

```
php artisan calisero:sms:test +40712345678 --from=YourApp --text="Test message"
```

#### SMS Status

[](#sms-status-7)

```
php artisan calisero:sms:status 019961d8-3338-700c-be17-10d061f03a5c
```

#### Verification Commands

[](#verification-commands-7)

Send a verification code with brand:

```
php artisan calisero:verification:send +40712345678 --brand=MyApp
```

Send a verification code with custom template:

```
php artisan calisero:verification:send +40712345678 --template="Your code is {code}" --expires-in=5
```

Check a verification code:

```
php artisan calisero:verification:check +40712345678 123456
```

#### Webhook Verification

[](#webhook-verification-7)

Enable webhook handling by setting `CALISERO_WEBHOOK_ENABLED=true`. The package will register a POST endpoint at `/calisero/webhook` (or your configured `CALISERO_WEBHOOK_PATH`).

If you also set `CALISERO_WEBHOOK_TOKEN=your-shared-secret`, the package will:

- Automatically append `?token=your-shared-secret` to the injected `callback_url` sent to Calisero (only when you did not supply a custom `callback_url`).
- Register a middleware that rejects any incoming webhook request not containing the correct `token` query parameter.

Requirements when token security is enabled:

- Each webhook request from Calisero must include the query parameter `token` with the exact configured value.
- If you manually override `callback_url`, you are responsible for including the `?token=...` segment yourself.
- If your explicit URL already contains a `token=` parameter, the library will not modify it.

Environment example:

```
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=super-secret-value
```

Example of explicit override (token already present, no modification by the library):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom secured callback',
    'callback_url' => 'https://example.com/custom-hook?token=' . urlencode(config('calisero.webhook.token')),
]);
```

Rotation tip: rotate the token by

1. Adding a temporary second endpoint (optional) or a maintenance window.
2. Updating `CALISERO_WEBHOOK_TOKEN`.
3. Redeploying and updating the callback URL in Calisero (or sending a new message to propagate the injected URL).

If you leave `CALISERO_WEBHOOK_TOKEN` empty, no token middleware is attached and the endpoint is publicly accessible (POST only). Consider other controls (IP allow-list, WAF) if you opt out of the token.

Listen for the events:

```
Event::listen(MessageSent::class, fn (MessageSent $e) => ...);
Event::listen(MessageDelivered::class, fn (MessageDelivered $e) => ...);
Event::listen(MessageFailed::class, fn (MessageFailed $e) => ...);
```

Statuses currently emitted (lifecycle):

- `sent` – the message was accepted and dispatched to the network
- `delivered` – the handset/network confirmed delivery
- `failed` – delivery permanently failed

Webhook payload example (flat structure):

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "sent",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": null,
  "remainingBalance": 999.43
}
```

When the same message is later delivered you will receive another webhook with:

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "delivered",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": "2025-09-19T12:00:24.000000Z",
  "remainingBalance": 999.43
}
```

A failed attempt would have `"status": "failed"` and usually a `deliveredAt` of `null`.

#### Automatic callback\_url Injection

[](#automatic-callback_url-injection-8)

If `CALISERO_WEBHOOK_ENABLED=true`, every `sendSms()` call **without** an explicit `callback_url` (or `callbackUrl`) automatically includes one pointing to the named route `calisero.webhook` (if registered) or a URL built from `app.url` + the configured path.
To override, supply your own `callback_url` parameter.
To disable injection, set `CALISERO_WEBHOOK_ENABLED=false` or omit the env variable.

Edge cases:

- If `app.url` is not set and the route helper fails, a root-relative path like `/calisero/webhook` is used.
- Passing either `callback_url` or `callbackUrl` prevents injection.

Example (override):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom callback',
    'callback_url' => 'https://example.com/custom-hook',
]);
```

### Artisan Commands

[](#artisan-commands-8)

The package provides several Artisan commands for testing and development:

#### Test SMS Sending

[](#test-sms-sending-8)

```
php artisan calisero:sms:test +40712345678 --from=YourApp --text="Test message"
```

#### SMS Status

[](#sms-status-8)

```
php artisan calisero:sms:status 019961d8-3338-700c-be17-10d061f03a5c
```

#### Verification Commands

[](#verification-commands-8)

Send a verification code with brand:

```
php artisan calisero:verification:send +40712345678 --brand=MyApp
```

Send a verification code with custom template:

```
php artisan calisero:verification:send +40712345678 --template="Your code is {code}" --expires-in=5
```

Check a verification code:

```
php artisan calisero:verification:check +40712345678 123456
```

#### Webhook Verification

[](#webhook-verification-8)

Enable webhook handling by setting `CALISERO_WEBHOOK_ENABLED=true`. The package will register a POST endpoint at `/calisero/webhook` (or your configured `CALISERO_WEBHOOK_PATH`).

If you also set `CALISERO_WEBHOOK_TOKEN=your-shared-secret`, the package will:

- Automatically append `?token=your-shared-secret` to the injected `callback_url` sent to Calisero (only when you did not supply a custom `callback_url`).
- Register a middleware that rejects any incoming webhook request not containing the correct `token` query parameter.

Requirements when token security is enabled:

- Each webhook request from Calisero must include the query parameter `token` with the exact configured value.
- If you manually override `callback_url`, you are responsible for including the `?token=...` segment yourself.
- If your explicit URL already contains a `token=` parameter, the library will not modify it.

Environment example:

```
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=super-secret-value
```

Example of explicit override (token already present, no modification by the library):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom secured callback',
    'callback_url' => 'https://example.com/custom-hook?token=' . urlencode(config('calisero.webhook.token')),
]);
```

Rotation tip: rotate the token by

1. Adding a temporary second endpoint (optional) or a maintenance window.
2. Updating `CALISERO_WEBHOOK_TOKEN`.
3. Redeploying and updating the callback URL in Calisero (or sending a new message to propagate the injected URL).

If you leave `CALISERO_WEBHOOK_TOKEN` empty, no token middleware is attached and the endpoint is publicly accessible (POST only). Consider other controls (IP allow-list, WAF) if you opt out of the token.

Listen for the events:

```
Event::listen(MessageSent::class, fn (MessageSent $e) => ...);
Event::listen(MessageDelivered::class, fn (MessageDelivered $e) => ...);
Event::listen(MessageFailed::class, fn (MessageFailed $e) => ...);
```

Statuses currently emitted (lifecycle):

- `sent` – the message was accepted and dispatched to the network
- `delivered` – the handset/network confirmed delivery
- `failed` – delivery permanently failed

Webhook payload example (flat structure):

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "sent",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": null,
  "remainingBalance": 999.43
}
```

When the same message is later delivered you will receive another webhook with:

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "delivered",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": "2025-09-19T12:00:24.000000Z",
  "remainingBalance": 999.43
}
```

A failed attempt would have `"status": "failed"` and usually a `deliveredAt` of `null`.

#### Automatic callback\_url Injection

[](#automatic-callback_url-injection-9)

If `CALISERO_WEBHOOK_ENABLED=true`, every `sendSms()` call **without** an explicit `callback_url` (or `callbackUrl`) automatically includes one pointing to the named route `calisero.webhook` (if registered) or a URL built from `app.url` + the configured path.
To override, supply your own `callback_url` parameter.
To disable injection, set `CALISERO_WEBHOOK_ENABLED=false` or omit the env variable.

Edge cases:

- If `app.url` is not set and the route helper fails, a root-relative path like `/calisero/webhook` is used.
- Passing either `callback_url` or `callbackUrl` prevents injection.

Example (override):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom callback',
    'callback_url' => 'https://example.com/custom-hook',
]);
```

### Artisan Commands

[](#artisan-commands-9)

The package provides several Artisan commands for testing and development:

#### Test SMS Sending

[](#test-sms-sending-9)

```
php artisan calisero:sms:test +40712345678 --from=YourApp --text="Test message"
```

#### SMS Status

[](#sms-status-9)

```
php artisan calisero:sms:status 019961d8-3338-700c-be17-10d061f03a5c
```

#### Verification Commands

[](#verification-commands-9)

Send a verification code with brand:

```
php artisan calisero:verification:send +40712345678 --brand=MyApp
```

Send a verification code with custom template:

```
php artisan calisero:verification:send +40712345678 --template="Your code is {code}" --expires-in=5
```

Check a verification code:

```
php artisan calisero:verification:check +40712345678 123456
```

#### Webhook Verification

[](#webhook-verification-9)

Enable webhook handling by setting `CALISERO_WEBHOOK_ENABLED=true`. The package will register a POST endpoint at `/calisero/webhook` (or your configured `CALISERO_WEBHOOK_PATH`).

If you also set `CALISERO_WEBHOOK_TOKEN=your-shared-secret`, the package will:

- Automatically append `?token=your-shared-secret` to the injected `callback_url` sent to Calisero (only when you did not supply a custom `callback_url`).
- Register a middleware that rejects any incoming webhook request not containing the correct `token` query parameter.

Requirements when token security is enabled:

- Each webhook request from Calisero must include the query parameter `token` with the exact configured value.
- If you manually override `callback_url`, you are responsible for including the `?token=...` segment yourself.
- If your explicit URL already contains a `token=` parameter, the library will not modify it.

Environment example:

```
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=super-secret-value
```

Example of explicit override (token already present, no modification by the library):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom secured callback',
    'callback_url' => 'https://example.com/custom-hook?token=' . urlencode(config('calisero.webhook.token')),
]);
```

Rotation tip: rotate the token by

1. Adding a temporary second endpoint (optional) or a maintenance window.
2. Updating `CALISERO_WEBHOOK_TOKEN`.
3. Redeploying and updating the callback URL in Calisero (or sending a new message to propagate the injected URL).

If you leave `CALISERO_WEBHOOK_TOKEN` empty, no token middleware is attached and the endpoint is publicly accessible (POST only). Consider other controls (IP allow-list, WAF) if you opt out of the token.

Listen for the events:

```
Event::listen(MessageSent::class, fn (MessageSent $e) => ...);
Event::listen(MessageDelivered::class, fn (MessageDelivered $e) => ...);
Event::listen(MessageFailed::class, fn (MessageFailed $e) => ...);
```

Statuses currently emitted (lifecycle):

- `sent` – the message was accepted and dispatched to the network
- `delivered` – the handset/network confirmed delivery
- `failed` – delivery permanently failed

Webhook payload example (flat structure):

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "sent",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": null,
  "remainingBalance": 999.43
}
```

When the same message is later delivered you will receive another webhook with:

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "delivered",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": "2025-09-19T12:00:24.000000Z",
  "remainingBalance": 999.43
}
```

A failed attempt would have `"status": "failed"` and usually a `deliveredAt` of `null`.

#### Automatic callback\_url Injection

[](#automatic-callback_url-injection-10)

If `CALISERO_WEBHOOK_ENABLED=true`, every `sendSms()` call **without** an explicit `callback_url` (or `callbackUrl`) automatically includes one pointing to the named route `calisero.webhook` (if registered) or a URL built from `app.url` + the configured path.
To override, supply your own `callback_url` parameter.
To disable injection, set `CALISERO_WEBHOOK_ENABLED=false` or omit the env variable.

Edge cases:

- If `app.url` is not set and the route helper fails, a root-relative path like `/calisero/webhook` is used.
- Passing either `callback_url` or `callbackUrl` prevents injection.

Example (override):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom callback',
    'callback_url' => 'https://example.com/custom-hook',
]);
```

### Artisan Commands

[](#artisan-commands-10)

The package provides several Artisan commands for testing and development:

#### Test SMS Sending

[](#test-sms-sending-10)

```
php artisan calisero:sms:test +40712345678 --from=YourApp --text="Test message"
```

#### SMS Status

[](#sms-status-10)

```
php artisan calisero:sms:status 019961d8-3338-700c-be17-10d061f03a5c
```

#### Verification Commands

[](#verification-commands-10)

Send a verification code with brand:

```
php artisan calisero:verification:send +40712345678 --brand=MyApp
```

Send a verification code with custom template:

```
php artisan calisero:verification:send +40712345678 --template="Your code is {code}" --expires-in=5
```

Check a verification code:

```
php artisan calisero:verification:check +40712345678 123456
```

#### Webhook Verification

[](#webhook-verification-10)

Enable webhook handling by setting `CALISERO_WEBHOOK_ENABLED=true`. The package will register a POST endpoint at `/calisero/webhook` (or your configured `CALISERO_WEBHOOK_PATH`).

If you also set `CALISERO_WEBHOOK_TOKEN=your-shared-secret`, the package will:

- Automatically append `?token=your-shared-secret` to the injected `callback_url` sent to Calisero (only when you did not supply a custom `callback_url`).
- Register a middleware that rejects any incoming webhook request not containing the correct `token` query parameter.

Requirements when token security is enabled:

- Each webhook request from Calisero must include the query parameter `token` with the exact configured value.
- If you manually override `callback_url`, you are responsible for including the `?token=...` segment yourself.
- If your explicit URL already contains a `token=` parameter, the library will not modify it.

Environment example:

```
CALISERO_WEBHOOK_ENABLED=true
CALISERO_WEBHOOK_PATH=calisero/webhook
CALISERO_WEBHOOK_TOKEN=super-secret-value
```

Example of explicit override (token already present, no modification by the library):

```
Calisero::sendSms([
    'to' => '+1234567890',
    'text' => 'Custom secured callback',
    'callback_url' => 'https://example.com/custom-hook?token=' . urlencode(config('calisero.webhook.token')),
]);
```

Rotation tip: rotate the token by

1. Adding a temporary second endpoint (optional) or a maintenance window.
2. Updating `CALISERO_WEBHOOK_TOKEN`.
3. Redeploying and updating the callback URL in Calisero (or sending a new message to propagate the injected URL).

If you leave `CALISERO_WEBHOOK_TOKEN` empty, no token middleware is attached and the endpoint is publicly accessible (POST only). Consider other controls (IP allow-list, WAF) if you opt out of the token.

Listen for the events:

```
Event::listen(MessageSent::class, fn (MessageSent $e) => ...);
Event::listen(MessageDelivered::class, fn (MessageDelivered $e) => ...);
Event::listen(MessageFailed::class, fn (MessageFailed $e) => ...);
```

Statuses currently emitted (lifecycle):

- `sent` – the message was accepted and dispatched to the network
- `delivered` – the handset/network confirmed delivery
- `failed` – delivery permanently failed

Webhook payload example (flat structure):

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "sent",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": null,
  "remainingBalance": 999.43
}
```

When the same message is later delivered you will receive another webhook with:

```
{
  "price": 0.0378,
  "sender": "CALISERO",
  "sentAt": "2025-09-19T11:59:44.000000Z",
  "status": "delivered",
  "messageId": "019961d8-3338-700c-be17-10d061f03a5c",
  "recipient": "+40742***350",
  "scheduleAt": "2025-09-19T11:59:42.000000Z",
  "deliveredAt": "2025-09-19T12:00:24.000000Z",
  "remainingBalance": 999.43
}
```

A failed attempt would have `"status": "failed"` and usually a `deliveredAt` of `null`.

#### Automatic callback\_url Injection

[](#automatic-callback_url-injection-11)

If `CALISERO_WEBHOOK_ENABLED=true`, every `sendSms()` call **without** an explicit `callback_url` (or `callbackUrl`) automatically includes one pointing to the named route `calisero.webhook` (if registered) or a URL built from `app.url` + the configured path.
To override, supply your own `callback_url` parameter.
To disable injection, set `CALISERO\_WEBHOOK\_ENABLED=false

###  Health Score

41

—

FairBetter than 89% of packages

Maintenance69

Regular maintenance activity

Popularity16

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity59

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~6 days

Total

8

Last Release

184d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b7ebafb7e824292b4c789bacc4d30d7cbb5a9003fb70840fc8cc72246b12fb61?d=identicon)[calisero](/maintainers/calisero)

---

Top Contributors

[![calisero](https://avatars.githubusercontent.com/u/232999039?v=4)](https://github.com/calisero "calisero (24 commits)")

---

Tags

laravelnotificationssmscalisero

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/calisero-laravel-sms/health.svg)

```
[![Health](https://phpackages.com/badges/calisero-laravel-sms/health.svg)](https://phpackages.com/packages/calisero-laravel-sms)
```

###  Alternatives

[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k25.9M107](/packages/laravel-cashier)[laravel/ui

Laravel UI utilities and presets.

2.7k134.9M601](/packages/laravel-ui)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

71510.9M66](/packages/laravel-mcp)[laravel-doctrine/orm

An integration library for Laravel and Doctrine ORM

8425.3M87](/packages/laravel-doctrine-orm)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M97](/packages/roots-acorn)[spatie/laravel-health

Monitor the health of a Laravel application

85810.0M83](/packages/spatie-laravel-health)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
