Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Authy Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

What it actually is. A notification channel — one channel class plus a message builder. It converts $user->notify(new X()) into an Authy API call. There is nothing to host, no migrations shipped, no routes, no UI. Reading the channel source takes about two minutes and will answer most questions faster than the README.

Install and wire it up.

composer require laravel-notification-channels/authy
php artisan vendor:publish --provider="NotificationChannels\Authy\AuthyServiceProvider"

Add AUTHY_KEY=... to .env. Package discovery registers the provider; you do not touch config/app.php. Open config/authy.php once and treat it as the whole configuration surface — it is also the file you will re-check the next time a staging box silently 500s because AUTHY_KEY was never added there.

Provision before you notify. The channel routes by Authy ID, not phone number. No Authy ID means no delivery, and there is no fallback that "figures it out." Get IDs onto users first and persist them:

// migration
$table->unsignedBigInteger('authy_id')->nullable()->unique()->after('phone');

// registration / backfill path
$authyId = Authy::register($user->email, $user->phone, $user->country_code);
$user->update(['authy_id' => $authyId]);

Verify the register() signature against the README pinned to the version you installed — it has changed shape across the org's packages, and a mismatch fails at runtime with an unhelpful message.

Expose it to the pipeline:

// app/Models/User.php
public function routeNotificationForAuthy(): ?string
{
    return $this->authy_id ? (string) $this->authy_id : null;
}

First use case: step-up SMS on login MFA. Not signup verification, not password reset — login MFA is the shortest route to something demoable that also survives a security review.

class SendTwoFactorToken extends Notification implements ShouldQueue
{
    public $tries = 1;

    public function via($notifiable): array
    {
        return $notifiable->authy_id ? [AuthyChannel::class, 'database'] : [];
    }

    public function toAuthy($notifiable)
    {
        return AuthyMessage::create()->sms();
    }
}

Then $user->notify(new SendTwoFactorToken()). Where to look first: the README's toAuthy() section for the builder methods (sms(), call(), push()), then the channel class itself.

Implementation Patterns

The package sends; it does not verify. This is the single most important thing to internalize day-to-day. There is no code check, no attempt counter, no session state, no rate limiting. The moment you accept that, you put a seam behind your own interface:

interface VerificationService
{
    public function sendChallenge(User $user, string $method): void;
    public function checkChallenge(User $user, string $token): bool;
}

One side wraps $user->notify(...); the other hits Authy's verify endpoint. When Authy sunsets or you migrate to Twilio Verify, only this class changes.

$ok = Http::withHeaders(['X-Authy-API-Key' => config('authy.key')])
    ->get("https://api.authy.com/protected/json/verify/{$token}/{$user->authy_id}")
    ->throw()
    ->json('success');

Pair that with a RateLimiter keyed user:{id} (5 attempts / 10 min), an authy_verified_at column, and a short-lived signed session flag. Without those, you have shipped an open verification oracle, not MFA.

Channel strategy is plain PHP. All three methods share one interface, so fallback is a match, not a second integration:

$method = match (true) {
    $user->hasPushEnabled()        => 'push',
    $user->recentSmsFailures() >= 2 => 'call',
    default                        => 'sms',
};
$user->notify(new SendTwoFactorToken($method));

Push is effectively free per attempt; voice is materially more expensive than SMS. That ordering is a cost decision as much as a UX one — keep it in exactly one place so finance and product can both find it.

On-demand recipients cover the pre-model cases (invite acceptance, phone verification before signup completes):

Notification::route('authy', $authyId)->notify(new SendTwoFactorToken());

This only works because the channel resolves through the generic routeNotificationFor('authy') path. If you override it, keep the anonymous case working.

Always queue, and always pair with database. ShouldQueue keeps a slow Authy round-trip off the login request. Adding database to via() gives you a delivery row for free — which is what you'll query when support asks "did the SMS actually go out?" See the retry gotcha below before you enable retries.

Testing pattern that actually catches regressions.

Notification::fake();
$user->notify(new SendTwoFactorToken());
Notification::assertSentTo($user, SendTwoFactorToken::class);

That covers the dispatch. For the verification side, Http::fake() the check endpoint and assert both success and 401. Then write the test that matters most: a user with a null authy_id should not blow up the login flow. That branch is the one that reaches production.

Backfill is a shipped artifact, not a one-off. An artisan command that provisions Authy IDs for existing users, plus a detection query for null/stale IDs on the login path with a graceful fallback factor (email OTP or TOTP) instead of a hard failure.

Gotchas and Tips

Route resolution returns an Authy ID, not a phone number. Returning $this->phone from routeNotificationForAuthy() is the most common integration bug in this package. It produces a valid-looking call that Authy rejects with an opaque error you'll spend an hour decoding.

Exceptions propagate straight out of notify(). A vendor 5xx or timeout during login is a 500 in the user's face. Wrap the send in try/catch, log, degrade to a fallback factor. Alert on aggregate failure rate rather than individual errors — a wrong AUTHY_KEY fails uniformly and silently for every user, and per-error alerting drowns you.

Queue retries re-send real tokens. A retried notification fires a second SMS: real money, and a user now holding two codes with no way to know which is live. Set public $tries = 1 (or a tight retryUntil) and handle failure by prompting a manual re-send rather than letting the queue do it.

Notification::fake() hides delivery failures. Correct tool for unit tests, but it also means the suite will never notice a revoked credential, a broken API key, or a wrong route column. Keep one integration test against the real sandbox, or a scheduled smoke check.

Config caching eats an hour in staging, regularly. If php artisan config:cache has run, editing .env or config/authy.php does nothing until you re-cache. Make it the first thing you check when "the key is definitely set."

Nothing here prevents SMS pumping or toll fraud. Per-user attempt caps, IP/geo velocity rules, and spend alerting on the Twilio side are entirely your problem. An unthrottled endpoint that triggers SMS is a billing incident waiting for a scanner.

Notifications are fire-and-forget and carry no state. Authy generates and holds the token. Your notification class should not try to embed or remember one — which means "resend the same code" is not a thing. Every send is a new challenge; design the UX around that rather than fighting it.

Extension points are thin by design — fork, don't patch. Need a custom HTTP client, proxy support, different retry semantics, or a repoint to Twilio Verify? The whole channel is one class plus a message builder. Keep VerificationService as the seam, treat the package as replaceable, pin the dependency, and assume upstream is frozen rather than maintained. A competent Laravel dev can swap the implementation in an afternoon.

Put the Authy EOL date on the calendar at adoption time. This is the failure mode with no in-app signal: the API keeps working right up until it doesn't. Scope the migration ticket on day one, not at sunset.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource