reshapify / sendseven-laravel
Unofficial. SendSeven for Laravel: configured client, verified webhooks as Laravel events, channel onboarding redirects, a shared rate limit and a doctor command.
Requires
- php: ^8.3
- illuminate/cache: ^12.0||^13.0
- illuminate/console: ^12.0||^13.0
- illuminate/contracts: ^12.0||^13.0
- illuminate/http: ^12.0||^13.0
- illuminate/routing: ^12.0||^13.0
- illuminate/support: ^12.0||^13.0
- reshapify/sendseven: ^0.1
- spatie/laravel-package-tools: ^1.16
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
- pestphp/pest-plugin-type-coverage: ^4.0
- phpstan/phpstan-strict-rules: ^2.0
- rector/rector: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Unofficial. Not affiliated with or endorsed by SendSeven GmbH or Laravel Holdings.
Laravel integration for reshapify/sendseven, the typed SDK for the SendSeven messaging API (WhatsApp, SMS, email, Telegram, Messenger, Instagram, RCS, browser push).
- Configured client: the
SendSevenfacade, or injectReshapify\SendSeven\Client. - Webhooks in one line:
Route::sendSevenWebhooks()answers SendSeven's verification challenge, verifies signatures, ignores redeliveries and dispatches typed Laravel events. - Customer onboarding:
SendSeven::connect()sends customers to SendSeven's connect page (WhatsApp Embedded Signup, Instagram, Messenger, Telegram…) and tells you when their channel arrives. - One rate limit for every process: web requests, queue workers and the scheduler share the token's 100-requests-a-minute budget through your cache.
php artisan sendseven:doctorchecks the token, the plan, tenancy and the webhook endpoint against the live API.- Fakes for the API and signed webhook deliveries.
Install
composer require reshapify/sendseven-laravel
php artisan vendor:publish --tag=sendseven-config # optional
Composer may ask whether to trust php-http/discovery, a plugin of the core SDK. Laravel already ships an HTTP client, so answering "no" is fine.
SENDSEVEN_API_TOKEN=s7_api_...
Send
use Reshapify\SendSeven\Laravel\Facades\SendSeven; SendSeven::messages()->send(to: '+4915112345678', channelId: $channelId, text: 'Your order has shipped.');
Every SendSeven endpoint is available; see the SDK docs. For a customer's own token use SendSeven::forToken($token); for a sub-account with a partner token use SendSeven::forTenant($tenantId).
Receive webhooks
// routes/web.php (CSRF is skipped for this route; deliveries are signed instead) Route::sendSevenWebhooks(); // POST /webhooks/sendseven, named sendseven.webhooks
php artisan sendseven:webhooks:register # creates the endpoint and prints SENDSEVEN_WEBHOOK_SECRET php artisan sendseven:doctor # confirms SendSeven verified it
Listen for the SDK's own event classes:
use Reshapify\SendSeven\Webhooks\Events\MessageReceived; final class StoreInboundMessage implements ShouldQueue { public function handle(MessageReceived $event): void { Inbox::store(from: $event->message->fromId, text: $event->message->text); } }
| Event | When |
|---|---|
Reshapify\SendSeven\Webhooks\Events\MessageReceived |
Someone messaged you |
…\MessageStatusUpdated |
Sent, delivered, read or failed (failed(), message->errorCode()) |
…\MessageReactionChanged, ChannelEvent, ContactEvent, ConversationEvent, UnknownEvent |
The rest |
Reshapify\SendSeven\Laravel\Events\ChannelConnected |
A channel was created, e.g. through a connect link |
Reshapify\SendSeven\Laravel\Events\WebhookReceived |
Every delivery, with the route parameters |
Each event ID is handled once (configurable: webhooks.deduplicate_for_seconds).
An endpoint per customer? Put a parameter in the URL and resolve its secret:
Route::sendSevenWebhooks('webhooks/sendseven/{workspace}'); // AppServiceProvider::boot() SendSeven::resolveWebhookSecretUsing( fn (Request $request) => Workspace::find($request->route('workspace'))?->sendseven_webhook_secret, );
Returning null answers 404. To use your own controller instead, add the Reshapify\SendSeven\Laravel\Webhooks\VerifyWebhookSignature middleware and read the event with VerifyWebhookSignature::event($request).
Let customers connect their channels
use Reshapify\SendSeven\Enums\ChannelType; use Reshapify\SendSeven\Onboarding\ConnectLink; use Reshapify\SendSeven\Onboarding\WhatsAppMode; public function store(Workspace $workspace) { $connection = SendSeven::connect( ConnectLink::for(ChannelType::WhatsApp) ->modes(whatsapp: [WhatsAppMode::Classic]) ->brandedAs(config('app.name')) ->redirectTo(route('channels.index')) ->singleUse(), ); $workspace->update(['sendseven_connect_link_id' => $connection->link->id]); return $connection; // a redirect; Inertia requests get a full-page visit }
public function handle(ChannelConnected $event): void { Workspace::firstWhere('sendseven_connect_link_id', $event->connectLinkId())?->channels()->create([...]); }
SendSeven runs Meta's Embedded Signup on its connect page; the SDK's onboarding guide covers modes, delegation and what customers see.
Rate limits
SendSeven allows 100 standard requests a minute per token. The add-on counts every request in your cache (set SENDSEVEN_RATE_LIMIT_STORE to a store all processes share, like redis), keeps 10% headroom, and pauses everyone when SendSeven answers 429. When the minute's budget is spent it throws RateLimitExceeded instead of sending:
public function handle(): void { try { SendSeven::messages()->send(...); } catch (RateLimitExceeded $exception) { $this->release($exception->retryAfter()); } }
With a partner token, rate_limit.tenant_share stops one busy tenant from starving the others.
Testing
SendSeven traffic goes through Laravel's HTTP client, so Http::fake(), Http::preventStrayRequests() and Http::assertSent() already cover it. Set SENDSEVEN_MAX_ATTEMPTS=1 in phpunit.xml so faked 5xx responses aren't retried.
Or script SendSeven's responses by endpoint:
$fake = SendSeven::fake([ 'POST /messages' => ['id' => 'msg_1', 'direction' => 'outbound', 'message_type' => 'text', 'status' => 'queued', 'created_at' => '2026-10-01T09:00:00Z'], ]); $this->post('/orders/1/ship'); $fake->assertSent('POST /messages', fn ($request) => $request->body['to'] === '+4915112345678');
Webhooks, signed as SendSeven signs them:
use Reshapify\SendSeven\Laravel\Testing\InteractsWithSendSevenWebhooks; $this->postSendSevenWebhook('/webhooks/sendseven', $this->sendSevenWebhookPayload('message.received', [ 'message' => ['id' => 'msg_1', 'direction' => 'inbound', 'message_type' => 'text', 'text' => 'Hi', 'from_id' => '+4915112345678'], ]))->assertOk();
Commands
| Command | Does |
|---|---|
sendseven:doctor |
Checks the token, plan, tenancy, webhook secret and endpoint, and the rate-limit store |
sendseven:sms-prices {country?} |
SMS prices per segment |
Webhook endpoints. Leave out id to act on the endpoint behind Route::sendSevenWebhooks(), or on the only one there is.
| Command | When you need it |
|---|---|
sendseven:webhooks:register {url?} |
Setting up: creates the endpoint, prints its secret once, confirms verification |
sendseven:webhooks:list |
Every endpoint with its state (active, suspended, unverified, inactive) and last error |
sendseven:webhooks:show {id?} |
One endpoint in detail |
sendseven:webhooks:deliveries {id?} {--status=failed} |
Messages aren't arriving: what SendSeven sent, and what your app answered |
sendseven:webhooks:retry {delivery} {id?} |
Re-send a delivery your app missed |
sendseven:webhooks:test {id?} |
Check the app accepts a delivery end to end |
sendseven:webhooks:activate {id?} |
After an outage: SendSeven suspends endpoints after repeated failures |
sendseven:webhooks:update {id?} {--url=} {--event=*} {--name=} |
The URL or events changed |
sendseven:webhooks:rotate {id?} |
A secret leaked: new secret, printed once (asks first) |
sendseven:webhooks:delete {id?} |
Remove an endpoint (asks first) |
For AI agents
With Laravel Boost, php artisan boost:install picks up this package's guidelines and its sendseven-development skill. Without it, point your agent at vendor/reshapify/sendseven-laravel/resources/boost/skills/sendseven-development/SKILL.md.
Security
Report vulnerabilities privately; see SECURITY.md.
License
MIT. Unofficial: not affiliated with or endorsed by SendSeven GmbH or Laravel Holdings. "SendSeven" and "Laravel" are trademarks of their owners and are used only to say what this package works with.