laravel maintained by watsy
watsy/laravel
Official Laravel package for Watsy: verification codes (OTP) for Iraq over WhatsApp, with automatic fallback to Telegram and SMS.
- Laravel 10, 11, 12 and 13 (PHP 8.1+); the client also works without Laravel.
Watsyfacade, config file, and a middleware that checks webhook signatures.- Errors carry a stable code and the message in English and Arabic.
composer require watsy/laravel
Add your keys to .env:
WATSY_KEY=wsy_live_...
WATSY_WEBHOOK_SECRET=whsec_...
To change the defaults, publish the config: php artisan vendor:publish --tag=watsy-config.
Send and check a code
use Watsy\Laravel\Facades\Watsy;
// 1. Send. Pass the user's IP so per-IP rate limits apply to them, not to your server.
$verification = Watsy::verify('07701234567', [
'client_ip' => $request->ip(),
'channels' => ['whatsapp', 'telegram'], // optional, in order
]);
session(['watsy_verification' => $verification['id']]);
// 2. Check what the user typed.
$check = Watsy::check(session('watsy_verification'), $request->input('code'));
if ($check['valid']) {
// signed in
}
Options for verify(): channels, client_ip, code_length (4–8), ttl_seconds (60–1800), locale (ar or en), metadata.
Retry safely with an idempotency key: the same key returns the first response instead of sending a second code.
Watsy::verify($phone, ['client_ip' => $request->ip()], idempotencyKey: "signup-{$user->id}");
Other calls
Watsy::verification($id); // status, channel used, messages and cost
Watsy::cancel($id); // the code stops working
Watsy::messages(20); // latest send attempts
Watsy::balance(); // ['balance' => 3000, 'currency' => 'IQD', ...]
Errors
use Watsy\Laravel\WatsyException;
try {
Watsy::verify($phone);
} catch (WatsyException $e) {
$e->errorCode; // e.g. insufficient_balance, rate_limited, validation_failed
$e->status; // HTTP status
$e->getMessage(); // English
$e->messageAr; // Arabic
$e->requestId; // quote it to support
}
Webhooks
Exclude the route from CSRF protection, and put the signature middleware on it:
use Watsy\Laravel\Http\Middleware\VerifyWatsySignature;
Route::post('/watsy/webhook', function (Request $request) {
$event = $request->json()->all();
if ($event['type'] === 'verification.verified') {
// $event['data'] is the verification
}
return response()->noContent();
})->middleware(VerifyWatsySignature::class);
Events: verification.sent, verification.failed, verification.verified, verification.expired. Outside a route you can check a payload yourself with Watsy\Laravel\Webhook::constructEvent($rawBody, $signatureHeader, $secret).
Without Laravel
$watsy = new Watsy\Laravel\WatsyClient(getenv('WATSY_KEY'));
$watsy->verify('07701234567', ['client_ip' => $_SERVER['REMOTE_ADDR']]);
Test mode
Test keys (wsy_test_…) are free and only send to the project's test numbers, with a fixed code (000000 for 6 digits).
Links
- Docs: https://docs.watsy.me
- Dashboard: https://app.watsy.me
- Support: support@watsy.me
MIT © akkadX