Skip to main content
Laravel Developer Guide PHP 8.2+

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.

Updated: August 2026
8 min read
Laravel 10 / 11 Ready
NepalOtpService.php
PSR-12
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();
    }
}
Target: NTC & Ncell Tunnels 100% Native HTTP

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.

1

Configuration & Setup

First, secure your API credentials. Never hardcode API keys in your classes. Add your NepalOTP key to your .env file:

.env Environment 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:

config/services.php PHP
return [
    // ... mailgun, postmark, etc.
    
    'nepalotp' => [
        'key' => env('NEPALOTP_API_KEY'),
    ],
];
2

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.

app/Services/NepalOtpService.php PHP 8.2+
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();
    }
}
3

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
app/Rules/NepaliPhone.php Validation
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.');
        }
    }
}
4

Controller Implementation

Inject NepalOtpService directly into your controller method using Laravel IoC Dependency Injection:

app/Http/Controllers/Auth/PhoneVerificationController.php Controller
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]);
        }
    }
}
5

Testing & Mocking with Pest / PHPUnit

Use Laravel's native Http::fake() facade to test phone verification without making real network calls:

tests/Feature/PhoneVerificationTest.php Pest PHP
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'));
});
Build in Laravel

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.