padosoft / laravel-rebel-channel-vonage
Vonage provider for Laravel Rebel Channels: phone verification via Vonage Verify (SMS/voice), plain SMS delivery, and signed delivery-receipt webhooks. Part of padosoft/laravel-rebel-*.
Package info
github.com/padosoft/laravel-rebel-channel-vonage
pkg:composer/padosoft/laravel-rebel-channel-vonage
Requires
- php: ^8.3
- illuminate/contracts: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- padosoft/laravel-rebel-channels: ^0.1
- padosoft/laravel-rebel-core: ^0.1
- spatie/laravel-package-tools: ^1.92
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
This package is auto-updated.
Last update: 2026-06-04 00:05:16 UTC
README
Send phone verifications and SMS through Vonage, the Rebel way. This package plugs Vonage Verify (SMS / voice) and the Vonage SMS API into
laravel-rebel-channelsas aVerificationProviderand aMessageDeliveryChannel— so you get Vonage's global delivery plus Rebel's fraud guard, rate limiting, fallback and audit on top. Part of thepadosoft/laravel-rebel-*suite.
Table of contents
- What it is
- Quick glossary
- Why this package
- Rebel + Vonage vs the alternatives
- Vonage dashboard setup (step by step)
- Installation
- Configuration
- Usage
- Delivery receipts (status webhook)
- Live tests against the real API
.env.example- Security notes
- Testing & License
What it is
A thin, well-tested Vonage provider for Rebel Channels. You don't call it directly — you
call the Channels VerificationRouter, and it routes through this provider (with Rebel's bot
gate, IRSF defences, per-number rate limit, fallback and audit around it).
It speaks two Vonage APIs:
- Vonage Verify — start + check one-time codes over SMS / voice. Vonage generates, sends and validates the OTP; you never store or generate the code yourself.
- Vonage SMS API — send a plain SMS (e.g. a transactional notification) as a
MessageDeliveryChannel.
A small gateway seam (VonageGateway) wraps the HTTP calls, so the whole thing is
unit-testable offline and has a real live test-suite for the actual API.
Depends on padosoft/laravel-rebel-core
and padosoft/laravel-rebel-channels.
┌─────────────────────────────────────────────┐
your app │ Channels VerificationRouter │
│ │ ├─ bot gate · IRSF guard · rate limit │
└───────▶│ ├─ fallback order ─────────┐ │
│ └─ HMAC'd audit trail ▼ │
│ VonageVerifyProvider │
└───────────────────────────────┬─────────────┘
│ VonageGateway (seam)
▼
Verify /verify/json · /verify/check/json
SMS /sms/json
│
delivery receipt ◀────┘ POST /rebel/vonage/status
(delivered / failed / cost) → audit event
Quick glossary
| Term | In plain words |
|---|---|
| Vonage Verify | A Vonage product that sends and checks one-time codes for you — you never store or generate the OTP. |
| API key / API secret | Your Vonage account credentials, shown on the dashboard home page. |
request_id |
The handle Vonage returns when you start a verification; you pass it back to check the code. |
| Brand | The sender name (≤18 alphanumeric chars) shown in the Verify message body. |
| DLR | Delivery receipt — Vonage's server-to-server callback telling you whether an SMS was delivered, and what it cost. |
| Channel | sms or voice. |
Why this package
| ★ | What | In short |
|---|---|---|
| ★★★ | Vonage Verify, fully wrapped | Start + check codes over SMS/voice; you never handle the OTP yourself. |
| ★★★ | Rebel guarantees for free | Inherits the Channels fraud guard (IRSF), rate limit, fallback and HMAC'd audit. |
| ★★ | SMS delivery too | Doubles as a MessageDeliveryChannel for plain transactional SMS via the Vonage SMS API. |
| ★★ | Never throws out | Any transport error becomes a clean provider_error, so the router can fall back to another provider. |
| ★★ | Offline-testable | A gateway seam + fake means your tests don't hit Vonage; a separate live suite does. |
| ★★ | Real delivery receipts | A signed DLR webhook records delivered/failed + cost so the panel shows real numbers. |
| ★ | Safe by default | No credentials → nothing registers, and no client is ever built with empty keys. |
Rebel + Vonage vs the alternatives
Sending an OTP with Vonage, four ways:
| Capability | Rebel + this package | Shopify | Vonage Verify SDK (direct) | Raw Vonage SMS + your own OTP |
|---|---|---|---|---|
| Send/check a code via Vonage | ✅ | ❌ | ✅ | ➖ (you build OTP logic) |
| You never store/generate the OTP | ✅ | ✅ | ✅ | ❌ |
| Anti toll-fraud / IRSF guard | ✅ | ❌ | ❌ | ❌ |
| Per-number rate limit + bot gate | ✅ | ➖ | ❌ | ❌ |
| Provider fallback to another vendor | ✅ | ❌ | ❌ | ❌ |
| Signed, phone-bound reference (anti replay) | ✅ | ❌ | ❌ | ❌ |
| Unified audit trail (number HMAC'd) | ✅ | ❌ | ❌ | ❌ |
| Delivery receipts → delivered-rate + cost in panel | ✅ | ❌ | ➖ | ➖ |
| Graceful failure → router fallback | ✅ | ❌ | ❌ | ❌ |
Legend: ✅ built-in · ➖ partial / hosted-only · ❌ not available. Vonage is excellent at delivery; this package keeps all of that and adds the Rebel fraud/routing/audit layer around it. Shopify is a closed, hosted commerce platform: it sends its own customer OTPs but lets you neither pick Vonage as the sender, self-host it, fall back across vendors, nor configure its fraud controls — a black box, not a developer-facing verification library.
Vonage dashboard setup (step by step)
- Create a Vonage account at dashboard.nexmo.com/sign-up. The free trial gives you a small credit — note that trial accounts can only send to whitelisted test numbers (add your test phone under Account → Test numbers).
- Grab your credentials: on the dashboard home page copy your API key and API secret (top of the page).
- (SMS sends only) decide your sender ID — an alphanumeric name (e.g.
RebelApp, where alphanumeric senders are allowed) or a Vonage virtual number you rent under Numbers → Buy numbers. Put it inVONAGE_SMS_FROM. - (Delivery receipts) set the Delivery Receipts webhook URL (under Account → API settings
→ SMS settings, or per-application) to
https://<your-host>/rebel/vonage/status(see below). - Put the values in your
.env(see below). Done — the provider auto-registers.
Pricing: Vonage Verify is billed per successful verification + the channel cost; SMS is billed per segment. The free trial credit is enough to test end-to-end. Always keep the Rebel geo allowlist (in
laravel-rebel-channels) tight to avoid IRSF charges.
Installation
composer require padosoft/laravel-rebel-channel-vonage
php artisan vendor:publish --tag="rebel-channel-vonage-config"
Add your credentials to .env:
VONAGE_API_KEY=xxxxxxxx VONAGE_API_SECRET=xxxxxxxxxxxxxxxx VONAGE_BRAND=Verify VONAGE_SMS_FROM=RebelApp
That's it — the provider registers itself into the Channels router under the key vonage.
Configuration
File config/rebel-channel-vonage.php:
| Key | Default | What it does |
|---|---|---|
api_key |
env(VONAGE_API_KEY) |
Vonage API key. |
api_secret |
env(VONAGE_API_SECRET) |
Vonage API secret. |
brand |
env(VONAGE_BRAND, 'Verify') |
Verify sender name (≤18 alphanumeric chars). |
sms_from |
env(VONAGE_SMS_FROM) |
Sender ID for plain SMS sends. |
channels |
['sms','voice'] |
Which Rebel channels this provider may handle. |
register_provider |
true |
Auto-register into the Channels registry (when credentials exist). |
webhook.enabled |
true |
Register the delivery-receipt callback endpoint. |
webhook.validate_signature |
false |
Validate the Vonage sig on the webhook (enable once you turn on request signing). |
webhook.signature_secret |
env(VONAGE_SIGNATURE_SECRET) |
The signature secret from the dashboard. |
webhook.signature_method |
md5hash |
Signature method: md5hash (default) or HMAC md5/sha1/sha256/sha512. |
webhook.path |
rebel/vonage/status |
The webhook route path. |
Usage
Verification (the common case)
You typically don't touch this package directly — you use the Channels router:
use Padosoft\Rebel\Channels\Enums\Channel; use Padosoft\Rebel\Channels\Routing\VerificationRouter; use Padosoft\Rebel\Core\Context\SecurityContext; use Padosoft\Rebel\Core\Contracts\KeyedHasher; use Padosoft\Rebel\Core\Identifiers\PhoneIdentifier; $router = app(VerificationRouter::class); $ctx = SecurityContext::fromRequest($request, app(KeyedHasher::class)); // Send a code (Vonage Verify delivers it) $start = $router->start(PhoneIdentifier::from('+39 333 1234567'), Channel::Sms, $ctx); // Keep $start->reference (the Vonage request_id) — you pass it back on check. $reference = $start->reference; // Check what the user typed $result = $router->check(PhoneIdentifier::from('+39 333 1234567'), $request->string('code'), $reference, $ctx); if ($result->approved()) { // verified! }
Note: Vonage checks the code against the
request_idreturned bystart()— not the phone number. Always store and pass back$start->reference. (Without it,check()fails closed withmissing_reference.)
To force Vonage specifically, set it first in the Channels fallback order:
// config/rebel-channels.php 'providers' => ['vonage'],
Plain SMS delivery
The provider also implements MessageDeliveryChannel, so you can send a transactional SMS:
use Padosoft\Rebel\Channels\Contracts\MessageDeliveryChannel; use Padosoft\Rebel\Channels\Enums\Channel; use Padosoft\Rebel\Channels\Routing\ProviderRegistry; $vonage = app(ProviderRegistry::class)->get('vonage'); if ($vonage instanceof MessageDeliveryChannel) { $result = $vonage->send( PhoneIdentifier::from('+39 333 1234567'), 'Your order has shipped!', Channel::Sms, $ctx, ); $result->accepted(); // true when Vonage accepted it for delivery }
Delivery receipts (status webhook)
Vonage knows whether an SMS was actually delivered, failed, and exactly how much it cost — but only it knows, unless you let it call you back. This package ships a delivery-receipt (DLR) webhook that turns those callbacks into Rebel audit events, so the admin panel's Channel Performance can show real delivered / failed rates and spend per channel.
What it records. On each callback the endpoint writes one audit event (recipient number stored only as a keyed HMAC, never in clear):
| Vonage status | Audit event_type |
|---|---|
delivered |
channel.verification.delivered |
failed, rejected, expired, undeliverable |
channel.verification.undelivered |
accepted, buffered, unknown, … |
channel.verification.dispatched |
Each event carries channel: 'sms', provider: 'vonage', the HMAC'd recipient, and a metadata
object:
{
"message_status": "delivered",
"price": 0.0333,
"price_unit": null,
"message_sid": "0A00000123ABCD01",
"error_code": "0"
}
It accepts both DLR shapes Vonage uses:
- the classic SMS-API DLR (
msisdn,messageId,err-code,price), and - the newer Messages-API status callback (
to,message_uuid,usage.price,usage.currency).
(Vonage quotes price as a positive string; we store the absolute value so spend sums cleanly,
and to stay consistent with the other Rebel channel providers. price/error_code are null when
absent.)
Set the webhook URL. In the Vonage dashboard, point the Delivery Receipts callback at your app:
https://<your-host>/rebel/vonage/status
The endpoint is enabled by default (REBEL_VONAGE_WEBHOOK=true); set it to false to drop the
route. The path is configurable via webhook.path.
Signature validation. The route has no auth middleware — Vonage posts server-to-server.
Vonage only signs callbacks when you enable request signing on the account; once you do, set
webhook.validate_signature=true and provide the signature_secret (and the matching
signature_method). Every request must then carry a valid sig (recomputed from the sorted params
- your secret); a missing or forged signature is rejected with 403 and nothing is recorded. A malformed or empty payload is acknowledged with 204 and recorded as nothing, so a junk callback never 500s.
REBEL_VONAGE_WEBHOOK=true # register the /rebel/vonage/status route REBEL_VONAGE_WEBHOOK_VALIDATE=false # set true once request signing is on VONAGE_SIGNATURE_SECRET= # the signature secret from the dashboard VONAGE_SIGNATURE_METHOD=md5hash # md5hash | md5 | sha1 | sha256 | sha512 REBEL_VONAGE_WEBHOOK_PATH=rebel/vonage/status
Build now, wire live later: the endpoint and its audit events exist today, so you can simulate Vonage callbacks (the test suite does exactly that) and the admin aggregates real delivered/cost data as soon as you set the DLR URL in Vonage.
Live tests against the real API
The offline suite uses a fake gateway. To exercise the real Vonage Verify API
(tests/Live), opt in explicitly — it sends a real message:
# .env (or shell env) REBEL_VONAGE_LIVE=1 VONAGE_TEST_PHONE=+39XXXXXXXXXX # a whitelisted test number on trial accounts vendor/bin/pest --group=live
Without REBEL_VONAGE_LIVE=1 or with any credential missing, the live tests self-skip,
so composer test and external PRs never trigger a send. In CI, supply the values as
secrets and set REBEL_VONAGE_LIVE=1 on a dedicated job.
.env.example
VONAGE_API_KEY=xxxxxxxx VONAGE_API_SECRET=xxxxxxxxxxxxxxxx VONAGE_BRAND=Verify VONAGE_SMS_FROM=RebelApp REBEL_VONAGE_REGISTER=true REBEL_VONAGE_WEBHOOK=true REBEL_VONAGE_WEBHOOK_VALIDATE=false VONAGE_SIGNATURE_SECRET= VONAGE_SIGNATURE_METHOD=md5hash REBEL_VONAGE_WEBHOOK_PATH=rebel/vonage/status # Live tests (opt-in: SENDS A REAL MESSAGE) REBEL_VONAGE_LIVE=0 VONAGE_TEST_PHONE=+391234567890
Security notes
- No client with empty keys: the Vonage gateway is only constructed when both the API key and secret are present.
- No exception leakage: transport errors are caught and returned as a generic
provider_error— Vonage internals never bubble up to your app or logs. - Explicit status mapping: only Vonage's
0is treated as success; any unexpected status fails closed (no bogus "pending" with an empty reference). - No PII in clear: the recipient number in delivery receipts is stored only as a keyed HMAC; OTPs and secrets are never logged.
- Keep IRSF defences on: pair this with the Channels geo allowlist / per-prefix cap to avoid premium-rate fraud charges.
🔋 Vibe coding with batteries included
This package ships AI batteries — so you (and your AI agent) can extend it correctly on the first try:
CLAUDE.md— a concise AI working guide (purpose, conventions, architecture, how to extend, Definition of Done). Plain Markdown, so Claude Code, Cursor, Copilot and Codex all read it.AGENTS.md— the agent/workflow contract (branch → PR → CI → tag/release, the gates)..claude/skills/— invocable skills (at leastrebel-package-dev) encoding the suite's TDD loop, the PHPStan-level-max recipes, the security/telemetry rules, and the release discipline.
Open the repo in your AI editor and just start — the rules, guardrails and extension recipes come
with it. PRs that follow the shipped CLAUDE.md pass CI (PHPStan max + Pest + Pint) and review the
first time around.
Testing & License
composer test # Pest (provider + gateway + webhook + Channels integration; live suite self-skips) composer phpstan # static analysis, level max composer pint # code style
License: MIT — see LICENSE. Part of the padosoft/laravel-rebel suite.
