Laravel Phone Verification
via NepalOTP
Learn the cleanest, most idiomatic way to implement OTP-based phone verification in your Laravel application using custom validation rules, service classes, and the native HTTP Client.
use Illuminate\Support\Facades\Http; class NepalOtpService { public function sendOtp(string $phone): array { return Http::withToken(config('services.nepalotp.key')) ->post('https://nepalotp.com/api/v1/otp/send', [ 'phone' => $phone ])->throw()->json(); } }
Laravel provides unparalleled scaffolding for email-based authentication, but verifying phone numbers requires orchestrating external API calls to a reliable SMS gateway. Wiring this logic directly into your controllers creates bloated, untestable code.
In this advanced guide, we will architect a robust phone verification flow. We will build a dedicated Service Class, utilize the powerful Illuminate\Support\Facades\Http facade, and create custom validation rules to ensure only valid Nepali phone numbers hit your database.
Configuration & Setup
First, secure your API credentials. Never hardcode API keys in your classes. Add your NepalOTP key to your .env file:
NEPALOTP_API_KEY=npot_live_your_secret_key_here
Next, map the environment variable in your config/services.php file so Laravel can cache configuration in production:
return [ // ... mailgun, postmark, etc. 'nepalotp' => [ 'key' => env('NEPALOTP_API_KEY'), ], ];
Creating the Service Class
To adhere to the Single Responsibility Principle (SRP), we abstract all HTTP communication with NepalOTP into a dedicated service. This makes your code clean and easily testable without firing network requests in unit tests.
namespace App\Services; use Illuminate\Support\Facades\Http; use Illuminate\Http\Client\PendingRequest; use Illuminate\Http\Client\RequestException; class NepalOtpService { protected string $baseUrl = 'https://nepalotp.com/api/v1'; /** * Configure base HTTP client with token and timeouts. */ protected function client(): PendingRequest { return Http::withToken(config('services.nepalotp.key')) ->acceptJson() ->timeout(5); } /** * Dispatch an OTP to a Nepali phone number. * @throws RequestException */ public function sendOtp(string $phone): array { return $this->client() ->post("{$this->baseUrl}/otp/send", [ 'phone' => $phone ]) ->throw() ->json(); } /** * Validate an OTP code against its corresponding ID. * @throws RequestException */ public function verifyOtp(string $otpId, string $code): array { return $this->client() ->post("{$this->baseUrl}/otp/verify", [ 'id' => $otpId, 'code' => $code ]) ->throw() ->json(); } }
Custom Validation Rule
Create a reusable Laravel Rule object to enforce the E.164 phone format required by NTC and Ncell telecom networks:
php artisan make:rule NepaliPhone
namespace App\Rules; use Closure; use Illuminate\Contracts\Validation\ValidationRule; class NepaliPhone implements ValidationRule { public function validate(string $attribute, mixed $value, Closure $fail): void { // Ensures format strictly matches +977 followed by 10 digits if (!preg_match('/^\+977\d{10}$/', $value)) { $fail('The :attribute must be a valid Nepali phone number starting with +977.'); } } }
Controller Implementation
Inject NepalOtpService directly into your controller method using Laravel IoC Dependency Injection:
namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use App\Rules\NepaliPhone; use App\Services\NepalOtpService; use Illuminate\Http\Request; use Illuminate\Http\Client\RequestException; class PhoneVerificationController extends Controller { public function store(Request $request, NepalOtpService $otpService) { $validated = $request->validate([ 'phone' => ['required', 'string', new NepaliPhone] ]); try { $result = $otpService->sendOtp($validated['phone']); session()->put('verification_otp_id', $result['data']['otp_id']); session()->put('verification_phone', $validated['phone']); return redirect()->route('verification.notice'); } catch (RequestException $e) { $msg = $e->response->json('message', 'Failed to dispatch OTP.'); return back()->withErrors(['phone' => $msg]); } } }
Testing & Mocking with Pest / PHPUnit
Use Laravel's native Http::fake() facade to test phone verification without making real network calls:
use Illuminate\Support\Facades\Http; test('it redirects to verification notice on successful otp dispatch', function () { Http::fake([ 'nepalotp.com/api/v1/otp/send' => Http::response([ 'success' => true, 'data' => ['otp_id' => 'test_id_123'] ], 200) ]); $response = $this->post('/register/phone', [ 'phone' => '+9779812345678' ]); $response->assertRedirect(route('verification.notice')); });
Ship OTP in 5 minutes
Claim your free developer key with 100 test credits. No credit card required.
Get API Key →Start verifying users today.
Get your API key in minutes. Test in sandbox. Ship when you're ready.