How to Send OTP with Symfony (2026)
Symfony OTP tutorial using StartMessaging. Uses HttpClient, ParameterBag for secrets, Form component for validation, and Session for storing the request ID.
StartMessaging Team
Engineering
Symfony’s service-container model and HttpClient make OTP integration straightforward. This tutorial uses StartMessaging.
Setup
symfony new otp-symfony --webapp
cd otp-symfony
composer require symfony/http-clientService Configuration
# .env
SM_API_KEY=sm_live_xxxxxxxxxxxxxxxx
SM_BASE_URL=https://api.startmessaging.com# config/services.yaml
services:
App\Service\StartMessagingService:
arguments:
$apiKey: '%env(SM_API_KEY)%'
$baseUrl: '%env(SM_BASE_URL)%'StartMessagingService
<?php
// src/Service/StartMessagingService.php
namespace App\Service;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Component\Uid\Uuid;
class StartMessagingService
{
public function __construct(
private HttpClientInterface $client,
private string $apiKey,
private string $baseUrl,
) {}
public function sendOtp(string $phoneNumber): array
{
$r = $this->client->request('POST', $this->baseUrl.'/otp/send', [
'headers' => ['X-API-Key' => $this->apiKey, 'Content-Type' => 'application/json'],
'json' => ['phoneNumber' => $phoneNumber, 'idempotencyKey' => Uuid::v4()->toRfc4122()],
'timeout' => 10,
]);
return $r->toArray()['data'];
}
public function verifyOtp(string $requestId, string $otpCode): bool
{
$r = $this->client->request('POST', $this->baseUrl.'/otp/verify', [
'headers' => ['X-API-Key' => $this->apiKey, 'Content-Type' => 'application/json'],
'json' => ['requestId' => $requestId, 'otpCode' => $otpCode],
]);
return $r->getStatusCode() === 200;
}
}Auth Controller
<?php
// src/Controller/AuthController.php
namespace App\Controller;
use App\Service\StartMessagingService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class AuthController extends AbstractController
{
#[Route('/auth/send-otp', methods: ['POST'])]
public function send(Request $request, StartMessagingService $sm): Response
{
$phone = $request->getPayload()->getString('phoneNumber');
$data = $sm->sendOtp($phone);
$request->getSession()->set('otp_req', $data['requestId']);
return $this->json(['expiresAt' => $data['expiresAt']]);
}
#[Route('/auth/verify-otp', methods: ['POST'])]
public function verify(Request $request, StartMessagingService $sm): Response
{
$code = $request->getPayload()->getString('otpCode');
$rid = $request->getSession()->get('otp_req');
if (!$rid) return $this->json(['error' => 'no active otp'], 400);
$ok = $sm->verifyOtp($rid, $code);
if (!$ok) return $this->json(['error' => 'invalid'], 401);
$request->getSession()->remove('otp_req');
$request->getSession()->set('userPhone', 'verified');
return $this->json(['verified' => true]);
}
}Form Types
Use Symfony\Component\Validator\Constraints to enforce E.164 phone shape and 4–8 digit OTP shape on incoming requests.
FAQ
Same flow in PHP/Laravel? See our Laravel guide.
Related Articles
Complete PHP tutorial for sending and verifying OTP via SMS using curl and Laravel HTTP client with the StartMessaging API. Includes service class and middleware patterns.
CodeIgniter 4 OTP tutorial using StartMessaging. Uses CURLRequest, Config files for keys, and Session for the request ID. Drop-in patterns for Indian dev shops.
WordPress OTP integration using StartMessaging. Custom plugin with REST API endpoints, options-page for the API key, and a shortcode-based login form. WooCommerce-friendly.
Ready to Send OTPs?
Integrate StartMessaging in 5 minutes. No DLT registration required.