padosoft/laravel-rebel-channels

Channel/provider abstraction (SMS/WhatsApp/voice) for Laravel Rebel: verification routing with fallback, cooldown, multi-dimensional rate limiting, and anti toll-fraud/IRSF defences. Part of padosoft/laravel-rebel-*.

Maintainers

Package info

github.com/padosoft/laravel-rebel-channels

pkg:composer/padosoft/laravel-rebel-channels

Statistics

Installs: 0

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-06-03 09:35 UTC

This package is auto-updated.

Last update: 2026-06-03 09:38:49 UTC


README

One safe, fault-tolerant pipe for phone verifications (SMS / WhatsApp / voice). You ask "verify this number"; Rebel Channels runs it through a bot gate, anti toll-fraud/IRSF defences, a per-number rate limit, and provider fallback — then audits every decision (number always HMAC'd). It is provider-agnostic: plug in laravel-rebel-channel-twilio (or your own). Part of the padosoft/laravel-rebel-* suite.

Laravel Rebel

Laravel 12|13 PHP 8.3+ PHPStan max Pest 4 anti IRSF MIT

Table of contents

What it is (and what it is not)

It is the routing + defence layer for sending one-time phone verifications. It does not talk to Twilio (or any vendor) itself — it defines the contracts and the guarded flow, and delegates the actual send to a provider package such as laravel-rebel-channel-twilio.

It is not an OTP generator (that's the provider's job, e.g. Twilio Verify), and it is not tied to one vendor — register several providers and it will fall back between them.

Depends on padosoft/laravel-rebel-core.

Quick glossary (one minute)

Term In plain words
Verification "Send a code to this number and let me check what the user typed."
Provider The vendor that actually sends/checks the code (e.g. Twilio Verify).
Channel The medium: sms, whatsapp, voice.
Fallback If provider A is down, automatically try provider B.
IRSF / toll-fraud International Revenue Share Fraud: attackers pump OTP traffic toward premium-rate numbers to cash in. Expensive if undefended.
Geo allowlist "Only send to these country prefixes" — the single most effective IRSF defence.
Per-prefix cap A velocity circuit breaker per number prefix, so a sudden spike toward one range trips.
Bot gate A check (reCAPTCHA/Turnstile…) that a human, not a script, triggered the send.

Why Rebel Channels — the moats

What In short
★★★ IRSF / toll-fraud defences built in Geo allowlist, prefix blocklist, and a per-prefix velocity circuit breaker — the stuff that saves real money.
★★★ Provider fallback Register Twilio + a backup; an outage on one silently rolls over to the next.
★★★ Tamper-evident references The check() handle is HMAC-signed and bound to the phone — no provider/channel injection, no cross-user replay.
★★ Bot gate + per-number rate limit Two more layers before a single euro is spent on a send.
★★ Audited, privacy-first Every decision is recorded with the number HMAC'd (never in clear).
★★ Vendor-agnostic Swap or combine providers without touching your app code.
Safe defaults Ships a cache-backed rate limiter and a no-op bot gate so it just works, then hardens as you configure it.

Rebel Channels vs the alternatives

Sending a verification SMS, compared:

Capability Rebel Channels Twilio Verify SDK (direct) Hand-rolled SMS + OTP
Send/check a code
Provider fallback on outage
Geo allowlist (IRSF) ➖ (manual in console)
Per-prefix velocity circuit breaker
Per-number rate limit / cooldown
Bot gate before spending
Reference signed + phone-bound (anti replay/injection)
Unified audit trail, number HMAC'd
Vendor-agnostic / swappable

Legend: ✅ built-in · ➖ partial / manual · ❌ not available. Twilio Verify is a great provider — Rebel Channels wraps it (and others) with the routing, fraud and audit layer your app would otherwise have to build and maintain itself.

How it works (step by step)

$router->start($phone, Channel::Sms, $context, $botToken)
        |
        v
[1] Bot gate        -> BotProtection::passes()?      no -> blocked('bot_denied')
[2] Fraud guard     -> blocklist / geo allowlist / per-prefix cap?  no -> blocked(reason)
[3] Rate limit      -> too many sends to this number?  yes -> blocked('rate_limited')
        |  (the attempt is counted here, BEFORE trying providers, so outages can't bypass it)
        v
[4] Provider fallback -> try providers in order; first that accepts wins
        |
        v
returns a PENDING result with a SIGNED, phone-bound reference

...later...

$router->check($phone, $code, $reference, $context)
        |
        v
verify the reference signature + phone binding -> delegate to the provider -> approved / failed

Installation (junior-proof)

Prerequisites: Laravel 12 or 13, PHP 8.3+, and padosoft/laravel-rebel-core. You also want at least one provider, e.g. padosoft/laravel-rebel-channel-twilio.

composer require padosoft/laravel-rebel-channels
php artisan vendor:publish --tag="rebel-channels-config"

Then register a provider (the Twilio package does this for you) and you're ready:

use Padosoft\Rebel\Channels\Enums\Channel;
use Padosoft\Rebel\Channels\Routing\VerificationRouter;
use Padosoft\Rebel\Core\Context\SecurityContext;
use Padosoft\Rebel\Core\Identifiers\PhoneIdentifier;

$router = app(VerificationRouter::class);
$start  = $router->start(PhoneIdentifier::from('+39 333 1234567'), Channel::Sms, SecurityContext::fromRequest($request));

Configuration (every option)

File config/rebel-channels.php:

Key Default What it does
providers [] Provider keys to try, in fallback order. Empty = every registered provider that supports the channel.
rate_limit.max_per_window 5 Max verification sends per phone+channel within the window.
rate_limit.window_seconds 3600 The rate-limit window length.
fraud.allowed_prefixes [] If non-empty, ONLY numbers starting with one of these E.164 prefixes are allowed (geo allowlist).
fraud.blocked_prefixes [] Numbers starting with one of these are always blocked.
fraud.per_prefix.length 3 How many leading chars of the E.164 number form the velocity bucket (3+39).
fraud.per_prefix.max_per_window 0 Per-prefix send cap (0 disables the circuit breaker).
fraud.per_prefix.window_seconds 3600 The per-prefix window length.

To actually gate bots, bind your own implementation of the core BotProtection contract (reCAPTCHA/Turnstile…); otherwise a permissive no-op default is used.

Usage examples

1. Start + check a verification

$start = $router->start($phone, Channel::Sms, $ctx);
// store $start->reference (already signed) for the check step

$result = $router->check($phone, $request->string('code'), $reference, $ctx);
if ($result->approved()) {
    // the number is verified
}

2. WhatsApp with SMS fallback — list both providers; the router rolls over:

// config/rebel-channels.php
'providers' => ['twilio', 'vonage'],

3. Lock down to your markets (kills most IRSF)

'fraud' => [
    'allowed_prefixes' => ['+39', '+1', '+44'], // only Italy, US, UK
    'per_prefix' => ['length' => 3, 'max_per_window' => 50, 'window_seconds' => 3600],
],

4. Pass a bot token (verified by your bound BotProtection):

$router->start($phone, Channel::Sms, $ctx, $request->string('captcha_token'));

5. Inspect the audit trail

use Padosoft\Rebel\Core\Models\RebelAuthEvent;

RebelAuthEvent::query()->where('event_type', 'channel.verification.blocked')->get(); // see WHY sends were stopped

.env.example

REBEL_CHANNELS_RL_MAX=5
REBEL_CHANNELS_RL_WINDOW=3600
REBEL_CHANNELS_PREFIX_LEN=3
REBEL_CHANNELS_PREFIX_MAX=0
REBEL_CHANNELS_PREFIX_WINDOW=3600

Security notes

  • IRSF defence in depth: geo allowlist + prefix blocklist + per-prefix velocity cap.
  • Rate limit can't be bypassed: the attempt is counted before providers are tried, so forcing provider failures does not grant unlimited sends.
  • Tamper-evident, phone-bound references: the check() handle is HMAC-signed and tied to the number; forged or cross-user references are rejected.
  • Privacy-first audit: every routing decision is recorded with the phone number HMAC'd; reasons are generic machine codes, never the OTP.
  • Atomic rate limiting: the default limiter uses the cache's atomic increment with a TTL set once per window (no slide, no under-count).

Testing & License

composer test      # Pest (router fallback, rate limit, bot gate, fraud guard, signed references, audit)
composer phpstan   # static analysis, level max
composer pint      # code style

License: MIT — see LICENSE. Part of the padosoft/laravel-rebel suite.