laravel-notification-channels/authy
Send Authy push notifications and verification codes from Laravel. This channel integrates Twilio Authy with Laravel's notification system, letting you deliver one-time passwords and approval requests through a simple notification class.
Architecture fit. The package is a delivery adapter, not an identity system. It slots into Laravel's existing notification pipeline as one more channel class, which means it inherits queueing, ShouldQueue, rate-limit middleware, Notification::fake(), and your existing audit/notification-logging path rather than introducing a parallel mechanism. That is a genuinely good architectural fit for a Laravel codebase already using Notifiable models — the integration surface is one toAuthy() method, one routeNotificationForAuthy() method, and one config file.
The critical architectural caveat is that the package is roughly half of an MFA feature. It sends challenges. It does not verify returned codes, enforce attempt limits, track session state, rate-limit, or manage fallback factors. Those are the security-critical halves, and they are ours to build. The correct architecture is to treat the package as an implementation detail behind a first-party VerificationService interface from day one — send on one side, verify against the Authy check endpoint on the other — so the package is replaceable without touching call sites. If we instead let AuthyChannel leak into controllers and Livewire components, we've coupled our security surface to an unmaintained MIT package with 57 stars.
Integration feasibility. Low build cost, moderate total cost. Composer install, config publish, AUTHY_KEY in .env, service provider discovered automatically. The non-obvious prerequisite is provisioning: the channel routes by Authy ID, not phone number. Every user needs an Authy ID created and persisted before a single notification works, which means a schema migration, a registration-path change, and a backfill command for existing users. That backfill is the real scheduling item — it gates any rollout to the existing user base.
Technical risk. Four risks dominate, in order:
$tries = 1 is mandatory, not a tuning parameter.Adjacent risk: exceptions propagate out of notify(). A vendor 5xx during login becomes a 500 to the end user unless we wrap it. And nothing in the package prevents SMS pumping or toll fraud — per-user attempt caps, velocity rules, and Twilio-side spend alerting are entirely ours.
Key questions for the team before committing:
composer.json support our Laravel/PHP version, verified against the last commit date rather than assumed? Does it pin twilio/sdk in a way that conflicts with our existing SDK dependency?authy_verified_at, rate limiter — and is that estimated and scheduled, or is it hiding inside the MFA estimate?authy_id or Authy is down? If the answer is "hard failure at login," we have built an outage amplifier.Stack fit. Requires a Laravel app using the native notification system with Notifiable models. Composer install with package discovery — no config/app.php registration. The entire configuration surface is config/authy.php plus AUTHY_KEY. Provisioning the Authy ID is the first real work: a nullable unique authy_id column on users (plus any other notifiable model that needs verification), populated on registration via the Authy register call, and exposed to the pipeline through routeNotificationForAuthy(): ?string.
The single most common integration bug is returning $this->phone from that route method instead of the Authy ID. It produces a valid-looking call that Authy rejects with an opaque error. Call this out in the PR review checklist explicitly.
Compatibility. Two dimensions to verify rather than assume: Laravel/PHP version support against composer.json and the last commit date, and twilio/sdk pin conflicts with anything already in our dependency tree. Pin the package version and treat upstream updates as frozen rather than expected — this is a fork-if-you-need-changes dependency, not a track-the-release-notes dependency.
Migration path. Two tracks, and they should be planned together:
VerificationService, run a provisioning backfill artisan command for existing users, detect null/stale IDs on the login path, and degrade to a fallback factor (email OTP or TOTP) rather than hard-failing.VerificationService's implementation changes. The interface, the rate limiter, the session flag, the notification classes, and every call site stay put. If we get the seam right, the migration to Twilio Verify is an afternoon of work plus a credential swap. If we get it wrong, it's a re-implementation.Sequencing. Roughly an afternoon to a token on a phone; 3–5 days to production-worthy. Order of work:
AUTHY_KEY in every environment including staging (a missing staging key fails silently and uniformly).routeNotificationForAuthy() and one notification class for the first use case — login step-up MFA, not signup verification or password reset. Login MFA is the shortest path to a security review clearance.VerificationService implementation: check endpoint, RateLimiter at 5 attempts / 10 minutes keyed on user:{id}, authy_verified_at, short-lived signed session flag.database channel added to via() for a free delivery audit record.Channel strategy without a second integration. Because SMS, voice, and push share one interface, fallback is plain PHP — push for push-enabled users, SMS for web-only, voice after repeated SMS failures. Encode the ordering in one location: push is effectively free per attempt, SMS is metered, voice is materially more expensive. That's a cost decision as much as a UX one.
Maintenance. Low ongoing code maintenance, high ongoing ownership awareness. The package is ~one class, MIT-licensed, and we should treat it as frozen — pin it and don't chase upstream. What requires real maintenance is everything around it: the VerificationService implementation, the rate limiter, the provisioning backfill, fallback logic, and the vendor relationship. Budget the EOL calendar reminder at adoption time, not at sunset; the failure mode here has zero in-app signal.
Support. No SLA, no enterprise support, no security questionnaire answered by the package author. Our support path splits cleanly: Twilio supports the Authy service, our team supports the adapter. For a security review, the honest framing is "MIT connector, one class, forkable in an afternoon, vendor is Twilio" — and back it with the actual fork plan. Do not offer reassurance without the plan.
Support-ticket volume will also come from UX, not just outages: users who received a code, missed it, and expect a "resend the same code" button that cannot exist. Design the copy and the flow around new-challenge-every-send.
Scaling. Scales the way Laravel queues scale — ShouldQueue keeps the vendor round-trip off the request path, which is the main throughput concern. Two real scaling constraints are commercial, not technical:
Failure modes to design against, in priority order:
| Failure | User-visible symptom | Mitigation |
|---|---|---|
| Wrong route column (phone, not Authy ID) | Valid-looking call, opaque vendor rejection | Explicit PR checklist item; test the missing-authy_id branch |
AUTHY_KEY missing in staging/env |
Uniform silent failure for all users | Config check at boot; alert on aggregate delivery-failure rate, not individual errors |
| Vendor 5xx/timeout during login | 500 returned to user | try/catch around notify(), log, degrade to fallback factor |
| Queue retry | Second SMS, second charge, user holds two codes | $tries = 1 or tight retryUntil; handle failure as a manual re-send prompt |
config:cache staleness |
Config edits have no effect, ~1 hour lost in staging | Documented in runbook; re-cache step in deploy |
Notification::fake() masking breakage |
Green test suite, broken prod key | One integration test or scheduled smoke check against the real sandbox |
Null/stale authy_id at login |
User locked out | Backfill command + graceful fallback factor (email OTP/TOTP) |
| Authy API EOL | Total, sudden loss of verification | Migration ticket scoped and funded on day one |
Ramp-up. Plan on an afternoon to get a token onto a phone and 3–5 days to make it production-worthy, plus a named workstream for the verification half. That workstream is the thing that makes MFA look cheaper and smaller than it is if left implicit — scope it explicitly or the estimate will be wrong by the largest single component.
Ramp order should be: one engineer owns VerificationService and the seam; login step-up MFA ships first as a time-boxed increment to clear the security review or enterprise blocker; signup verification, account recovery, and re-verification before sensitive changes (password, email, payout details, admin grants) follow on the same primitive. Those recovery and step-up flows are frequently higher value than login MFA on both support-ticket volume and fraud loss, and they cost almost nothing extra once the seam exists.
The PM framing for stakeholders: this is a small, free connector that turns "gate login behind two-factor" into a sprint-sized deliverable and defers the platform work — a legitimate unblock-the-deal trade, provided the migration to Twilio Verify is scoped and funded in the same planning cycle rather than discovered at sunset. Scrutinize the vendor contract, pricing, and EOL timeline. The package is replaceable; the vendor decision is not.
How can I help you explore Laravel packages today?