PHPackages                             dev1/whatspass - 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. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. dev1/whatspass

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

dev1/whatspass
==============

Send OTP authentication messages via WhatsApp using Meta's official Cloud API. Supports Laravel and Symfony.

v1.0.0(2mo ago)01↓100%MITPHPPHP ^8.2CI passing

Since Mar 11Pushed 1mo agoCompare

[ Source](https://github.com/DEV1-Softworks/whatspass)[ Packagist](https://packagist.org/packages/dev1/whatspass)[ RSS](/packages/dev1-whatspass/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (8)Versions (2)Used By (0)

Whatspass
=========

[](#whatspass)

[![CI](https://github.com/dev1/whatspass/actions/workflows/ci.yml/badge.svg)](https://github.com/dev1/whatspass/actions/workflows/ci.yml)[![Latest Version on Packagist](https://camo.githubusercontent.com/debfe0eb37b93456f9bf9ba624efa56e59e0afe85abd3c23e78c16a9eb007ae1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f646576312f7768617473706173732e737667)](https://packagist.org/packages/dev1/whatspass)[![PHP Version](https://camo.githubusercontent.com/3944db421ffc611e9c960f2bc6e8d371800cc30f3fb93ec990708a839422927c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f646576312f7768617473706173732e737667)](https://packagist.org/packages/dev1/whatspass)[![License](https://camo.githubusercontent.com/c9a4a1016b2580981fe8c4113c34080fa2d7c7025cb0f8e73ec4ced6a3ba2893/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f646576312f7768617473706173732e737667)](LICENSE.md)

**Send OTP authentication codes via WhatsApp using Meta's official Cloud API.**

Whatspass is a framework-agnostic PHP library with first-class support for **Laravel** and **Symfony**. It talks directly to Meta's WhatsApp Business Cloud API — no third-party intermediaries, no extra billing layers.

---

Requirements
------------

[](#requirements)

RequirementVersionPHP≥ 8.2Laravel (optional)11 or 12Symfony (optional)6.4 or 7.x---

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

[](#installation)

```
composer require dev1/whatspass
```

---

Before You Start — Meta Setup
-----------------------------

[](#before-you-start--meta-setup)

You need a **Meta for Developers** account with a WhatsApp Business App. Here is the one-time setup:

1. Go to [developers.facebook.com](https://developers.facebook.com) and create an App (type: **Business**).
2. Add the **WhatsApp** product to your app.
3. In *WhatsApp → API Setup*, copy your:
    - **Phone Number ID** — the numeric ID that identifies the number you send from.
    - **Temporary access token** — or generate a permanent System User token from the Business Manager.
4. Create and get a **Message Template** approved in the Meta Business Manager. For OTP delivery, a simple body like *"Your verification code is {{1}}."* is all you need.

> **Note:** Template messages work at any time. Free-form text messages (`type: text`) only work within a 24-hour customer-care window (i.e., the recipient must have messaged you first).

---

Laravel
-------

[](#laravel)

### 1. Publish the config

[](#1-publish-the-config)

```
php artisan vendor:publish --tag=whatspass-config
```

This creates `config/whatspass.php`.

### 2. Set environment variables

[](#2-set-environment-variables)

Copy `.env.example` entries to your `.env` file and fill in your credentials:

```
# Required
WHATSPASS_PHONE_NUMBER_ID=123456789012345
WHATSPASS_ACCESS_TOKEN=your_access_token_here

# Optional (defaults shown)
WHATSPASS_API_VERSION=v19.0
WHATSPASS_TEMPLATE_NAME=otp_authentication
WHATSPASS_LANGUAGE_CODE=en_US
WHATSPASS_OTP_LENGTH=6
WHATSPASS_OTP_EXPIRY=300
WHATSPASS_ALPHANUMERIC_OTP=false
```

### 3. Send an OTP

[](#3-send-an-otp)

**Using the Facade:**

```
use Dev1\Whatspass\Laravel\Facades\Whatspass;

// Generate a code and send it in one step
$result = Whatspass::generateAndSend('+15551234567');

$otp      = $result['otp'];       // "483920"  — store this to verify later
$response = $result['response'];  // raw Meta API response
```

**Using dependency injection:**

```
use Dev1\Whatspass\Contracts\WhatspassServiceInterface;

class AuthController extends Controller
{
    public function __construct(
        private readonly WhatspassServiceInterface $whatspass,
    ) {}

    public function sendOtp(Request $request): JsonResponse
    {
        $phone = $request->input('phone'); // e.g. "+15551234567"

        $result = $this->whatspass->generateAndSend($phone);

        // Store $result['otp'] in your session/cache to verify later
        session(['otp' => $result['otp'], 'otp_phone' => $phone]);

        return response()->json(['message' => 'OTP sent.']);
    }
}
```

### Configuration reference

[](#configuration-reference)

```
// config/whatspass.php
return [
    'phone_number_id'       => env('WHATSPASS_PHONE_NUMBER_ID'),
    'access_token'          => env('WHATSPASS_ACCESS_TOKEN'),
    'api_version'           => env('WHATSPASS_API_VERSION', 'v19.0'),
    'base_url'              => env('WHATSPASS_BASE_URL', 'https://graph.facebook.com'),
    'default_template_name' => env('WHATSPASS_TEMPLATE_NAME', 'otp_authentication'),
    'default_language_code' => env('WHATSPASS_LANGUAGE_CODE', 'en_US'),
    'otp_length'            => (int) env('WHATSPASS_OTP_LENGTH', 6),     // 4–12 characters
    'otp_expiry'            => (int) env('WHATSPASS_OTP_EXPIRY', 300),   // seconds (≥ 60)
    'alphanumeric_otp'      => (bool) env('WHATSPASS_ALPHANUMERIC_OTP', false),
];
```

### Rate limiting (Laravel)

[](#rate-limiting-laravel)

By default the library uses a no-op rate limiter. To enforce per-phone-number limits in production, bind your own implementation to `RateLimiterInterface`:

```
// app/Providers/AppServiceProvider.php
use Dev1\Whatspass\Contracts\RateLimiterInterface;
use App\Services\RedisWhatspassRateLimiter;

public function register(): void
{
    $this->app->bind(RateLimiterInterface::class, RedisWhatspassRateLimiter::class);
}
```

Example implementation using Laravel Cache:

```
use Dev1\Whatspass\Contracts\RateLimiterInterface;
use Dev1\Whatspass\Exceptions\RateLimitExceededException;
use Illuminate\Support\Facades\Cache;

class CacheRateLimiter implements RateLimiterInterface
{
    public function __construct(
        private readonly int $maxAttempts = 3,
        private readonly int $decaySeconds = 60,
    ) {}

    public function attempt(string $phoneNumber): void
    {
        $key   = 'whatspass:' . hash('sha256', $phoneNumber);
        $hits  = (int) Cache::get($key, 0);

        if ($hits >= $this->maxAttempts) {
            throw new RateLimitExceededException($phoneNumber);
        }

        Cache::put($key, $hits + 1, $this->decaySeconds);
    }
}
```

---

Symfony
-------

[](#symfony)

### 1. Register the bundle

[](#1-register-the-bundle)

```
// config/bundles.php
return [
    // ...
    Dev1\Whatspass\Symfony\WhatspassBundle::class => ['all' => true],
];
```

### 2. Add configuration

[](#2-add-configuration)

```
# config/packages/whatspass.yaml
whatspass:
    phone_number_id:       '%env(WHATSPASS_PHONE_NUMBER_ID)%'
    access_token:          '%env(WHATSPASS_ACCESS_TOKEN)%'
    api_version:           'v19.0'
    default_template_name: 'otp_authentication'
    default_language_code: 'en_US'
    otp_length:            6
    otp_expiry:            300
    alphanumeric_otp:      false
```

Set the required values in your `.env` file:

```
WHATSPASS_PHONE_NUMBER_ID=123456789012345
WHATSPASS_ACCESS_TOKEN=your_access_token_here
```

### 3. Send an OTP

[](#3-send-an-otp-1)

```
use Dev1\Whatspass\Contracts\WhatspassServiceInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;

class AuthController extends AbstractController
{
    public function __construct(
        private readonly WhatspassServiceInterface $whatspass,
    ) {}

    public function sendOtp(string $phone): JsonResponse
    {
        $result = $this->whatspass->generateAndSend($phone);

        // Store $result['otp'] in your session/cache for verification
        return new JsonResponse(['message' => 'OTP sent.']);
    }
}
```

### Rate limiting (Symfony)

[](#rate-limiting-symfony)

Register your implementation as an alias for `RateLimiterInterface`:

```
# config/services.yaml
services:
    App\Service\WhatspassRateLimiter:
        arguments:
            $maxAttempts: 3
            $decaySeconds: 60

    Dev1\Whatspass\Contracts\RateLimiterInterface:
        alias: App\Service\WhatspassRateLimiter
```

---

Framework-agnostic usage
------------------------

[](#framework-agnostic-usage)

No framework? No problem. Instantiate everything manually:

```
use Dev1\Whatspass\OtpGenerator;
use Dev1\Whatspass\WhatspassClient;
use Dev1\Whatspass\WhatspassConfig;
use Dev1\Whatspass\WhatspassService;

$config = WhatspassConfig::fromArray([
    'phone_number_id' => getenv('WHATSPASS_PHONE_NUMBER_ID'),
    'access_token'    => getenv('WHATSPASS_ACCESS_TOKEN'),
    'otp_length'      => 6,
]);

$service = new WhatspassService(
    config:    $config,
    client:    new WhatspassClient($config),
    generator: new OtpGenerator(),
);

// Generate and send
$result = $service->generateAndSend('+15551234567');
echo $result['otp']; // "749201"

// Or send a code you generated yourself
$otp = $service->generateOtp();
$service->sendOtp('+15551234567', $otp);
```

---

API Reference
-------------

[](#api-reference)

### `WhatspassService`

[](#whatspassservice)

All methods are available through the facade, dependency injection, or direct instantiation.

#### `generateOtp(?int $length, ?bool $alphanumeric): string`

[](#generateotpint-length-bool-alphanumeric-string)

Generate a cryptographically secure OTP code.

```
$otp = $service->generateOtp();                   // "483920"   (6-digit numeric, from config)
$otp = $service->generateOtp(length: 8);          // "20938471"
$otp = $service->generateOtp(alphanumeric: true); // "4aB9Qz"
```

#### `sendOtp(string $phone, string $otp, array $options): array`

[](#sendotpstring-phone-string-otp-array-options-array)

Send an existing OTP to a phone number. Returns the raw Meta API response.

```
// Template message (default)
$response = $service->sendOtp('+15551234567', '483920');

// Free-form text message
$response = $service->sendOtp('+15551234567', '483920', [
    'type'           => 'text',
    'custom_message' => 'Your Acme code is {otp}. Expires in 5 minutes.',
]);

// Override template per message
$response = $service->sendOtp('+15551234567', '483920', [
    'template_name' => 'acme_otp_es',
    'language_code' => 'es_ES',
]);
```

OptionTypeDefaultDescription`type``string``'template'``'template'` or `'text'``template_name``string`config valueOverride the Meta template name`language_code``string`config valueBCP-47 language code (e.g. `pt_BR`)`custom_message``string`—Text body for `type: text`. Use `{otp}` as placeholder#### `generateAndSend(string $phone, array $options): array`

[](#generateandsendstring-phone-array-options-array)

Generate a code and send it in one step.

```
$result = $service->generateAndSend('+15551234567');

$result['otp'];      // "483920"  — persist this to verify the user later
$result['response']; // Meta API response array
```

Accepts the same `$options` as `sendOtp()`, plus:

OptionTypeDefaultDescription`otp_length``int`config valueOverride OTP length for this call`alphanumeric_otp``bool`config valueOverride alphanumeric flag for this call#### `send(OtpMessage $message): array`

[](#sendotpmessage-message-array)

Send a manually constructed `OtpMessage` object directly.

```
use Dev1\Whatspass\MessageType;
use Dev1\Whatspass\OtpMessage;

$message = new OtpMessage(
    to:            '+15551234567',
    otp:           '839201',
    type:          MessageType::Text,
    customMessage: 'Hi! Your login code is {otp}.',
);

$response = $service->send($message);
```

---

Phone Number Format
-------------------

[](#phone-number-format)

Phone numbers are automatically normalized to **E.164** format. All of these are accepted:

```
+1 (555) 123-4567  →  +15551234567
+1-555-123-4567    →  +15551234567
15551234567        →  +15551234567

```

An `InvalidPhoneNumberException` is thrown if the number cannot be normalized (e.g. too short, longer than 30 characters, starts with `0`, or contains letters).

---

Error Handling
--------------

[](#error-handling)

```
use Dev1\Whatspass\Exceptions\ApiException;
use Dev1\Whatspass\Exceptions\InvalidConfigException;
use Dev1\Whatspass\Exceptions\InvalidPhoneNumberException;
use Dev1\Whatspass\Exceptions\RateLimitExceededException;

try {
    $result = $service->generateAndSend($phone);
} catch (RateLimitExceededException $e) {
    // Too many OTP requests for this phone number
    return response()->json(['message' => 'Too many requests. Please wait and try again.'], 429);
} catch (InvalidPhoneNumberException $e) {
    // The phone number format is invalid
    logger()->warning('Bad phone number', ['error' => $e->getMessage()]);
} catch (ApiException $e) {
    // Meta API returned an error (4xx/5xx) or the connection failed
    logger()->error('WhatsApp API error', ['code' => $e->getCode()]);
} catch (InvalidConfigException $e) {
    // Misconfigured phone_number_id, access_token, otp_length, etc.
}
```

### Exception hierarchy

[](#exception-hierarchy)

```
\RuntimeException
└── WhatspassException
    ├── ApiException              — Meta API / network errors
    └── RateLimitExceededException — Rate limit hit for a phone number

\InvalidArgumentException
├── InvalidConfigException        — Bad configuration values
└── InvalidPhoneNumberException   — Unparseable phone number

```

---

Security
--------

[](#security)

The library enforces the following security constraints out of the box:

- **HTTPS only** — `base_url` must start with `https://`. Plain HTTP is rejected at config-load time.
- **TLS verification** — Guzzle is explicitly configured with `verify: true`. It cannot be silently disabled.
- **Numeric Phone Number ID** — `phone_number_id` must be all digits, matching Meta's format.
- **Phone masking in logs** — recipients are logged as `+155*****67` to avoid PII leakage.
- **Redacted API errors** — only `error_code` and `error_type` are logged; full Meta error bodies are never written to logs.
- **Cryptographic OTP generation** — all codes are generated with `random_int()` (CSPRNG).
- **ReDoS guard** — phone number inputs longer than 30 characters are rejected before any regex processing.

---

Logging
-------

[](#logging)

`WhatspassClient` accepts any PSR-3 logger. In Laravel and Symfony it is injected automatically. When provided, it logs:

- **`debug`** — before every request (masked recipient, message type)
- **`info`** — on success (masked recipient, Meta message ID)
- **`error`** — on API errors (HTTP status, error code and type only) and connection failures (exception class only)

Phone numbers are always masked in log output (e.g. `+155*****67`).

---

Testing
-------

[](#testing)

```
# Run the test suite
composer test

# Run with code coverage report (requires Xdebug or PCOV)
composer test:coverage
```

### Mocking in your own tests

[](#mocking-in-your-own-tests)

The library uses Guzzle's `MockHandler`, making it straightforward to test your own code without hitting the real API:

```
use Dev1\Whatspass\OtpGenerator;
use Dev1\Whatspass\WhatspassClient;
use Dev1\Whatspass\WhatspassConfig;
use Dev1\Whatspass\WhatspassService;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;

$mock = new MockHandler([
    new Response(200, [], json_encode([
        'messaging_product' => 'whatsapp',
        'messages' => [['id' => 'wamid.test123']],
    ])),
]);

$httpClient = new Client(['handler' => HandlerStack::create($mock)]);
$config     = WhatspassConfig::fromArray([
    'phone_number_id' => '123456789012345',
    'access_token'    => 'test-token',
]);

$service = new WhatspassService(
    config:    $config,
    client:    new WhatspassClient($config, $httpClient),
    generator: new OtpGenerator(),
);

$result = $service->generateAndSend('+15551234567');
// No real HTTP request is made
```

---

CI / CD
-------

[](#ci--cd)

The repository ships with two GitHub Actions workflows:

WorkflowTriggerWhat it does**CI** (`.github/workflows/ci.yml`)Push / PR to `main`, `master`, `develop`Runs the test suite on PHP 8.2, 8.3, and 8.4, and enforces ≥ 80% coverage**Release** (`.github/workflows/release.yml`)Push of a `v*.*.*` tagRuns tests, then creates a GitHub Release with auto-generated notes### Publishing a release

[](#publishing-a-release)

```
git tag v1.0.0
git push origin v1.0.0
```

The Release workflow runs the full test suite and creates a GitHub Release automatically.

---

License
-------

[](#license)

The MIT License (MIT). See [LICENSE.md](LICENSE.md) for details.

Made in Mexico with ❤️ by [DEV1 Labs](https://dev1.mx/labs), part of DEV1 Softworks.

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance88

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

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

Unknown

Total

1

Last Release

60d ago

### Community

Maintainers

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

---

Top Contributors

[![RZEROSTERN](https://avatars.githubusercontent.com/u/3065243?v=4)](https://github.com/RZEROSTERN "RZEROSTERN (8 commits)")

---

Tags

symfonylaravelotpAuthentication2fawhatsappmetaMFAwhatsapp-api

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/dev1-whatspass/health.svg)

```
[![Health](https://phpackages.com/badges/dev1-whatspass/health.svg)](https://phpackages.com/packages/dev1-whatspass)
```

###  Alternatives

[ellaisys/aws-cognito

AWS Cognito package that allows Auth and other related features using the AWS SDK for PHP

120220.7k1](/packages/ellaisys-aws-cognito)[sicaboy/laravel-mfa

A Laravel package of Multi-factor Authentication (MFA/2FA) with a middleware.

101.2k](/packages/sicaboy-laravel-mfa)

PHPackages © 2026

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