laravel-otpiq maintained by levilabs
Laravel OTPIQ SDK
A modern Laravel SDK for OTPIQ — SMS, WhatsApp, and Telegram OTP delivery, smart fallback routing, delivery webhooks, CSV reports, and WhatsApp Business campaigns, with typed DTOs, multi-project config, and optional message persistence.
Maintained by Levi Labs — built by Nizam Omer
Official API docs: docs.otpiq.com · Dashboard: app.otpiq.com
Table of contents
- Requirements
- Installation
- Configuration
- Provider routing (
OTPIQ_DEFAULT_PROVIDER) - Multiple OTPIQ projects
- API reference coverage
- Send messages
- WhatsApp campaigns
- Sender IDs, pricing & delivery reports
- Track delivery
- Delivery webhooks
- WhatsApp inbound webhooks
- Facades & services
- Persistence & sync
- Testing
- Security
- License
Requirements
- PHP 8.2+ (Laravel 13.x requires PHP 8.3+ on the application)
- Laravel 11.x, 12.x, or 13.x
- An OTPIQ project API key from app.otpiq.com
Installation
composer require levilabs/laravel-otpiq
php artisan vendor:publish --tag=otpiq-config
php artisan migrate
Configuration
Publish copies config/otpiq.php. Set credentials in .env:
# Which entry in config('otpiq.projects') to use by default
OTPIQ_PROJECT=default
# Primary project (maps to projects.default in config)
OTPIQ_API_KEY=sk_live_your_key_here
OTPIQ_BASE_URL=https://api.otpiq.com/api
# Default channel routing when a send DTO omits provider (see table below)
OTPIQ_DEFAULT_PROVIDER=whatsapp-telegram-sms
# Optional: merged into every send unless you pass DeliveryReportConfig on the DTO
OTPIQ_WEBHOOK_URL=https://your-app.com/webhooks/otpiq/delivery
OTPIQ_WEBHOOK_SECRET=your_webhook_secret
OTPIQ_WEBHOOK_REPORT_TYPE=final
# Optional HTTP tuning
OTPIQ_HTTP_TIMEOUT=15
OTPIQ_HTTP_RETRY_TIMES=1
OTPIQ_HTTP_RETRY_SLEEP_MS=200
API keys
| Key prefix | Use |
|---|---|
sk_live… |
Production — messages are sent to the phoneNumber you pass in the API. |
sk_dev… |
Development — OTPIQ always delivers to the dev phone number you set in the dashboard; the number in your code is ignored. Never deploy dev keys. |
Guide: How dev keys work
Phone numbers
OTPIQ expects digits only, usually Iraq format without + or spaces (e.g. 9647501234567). The SDK normalizes via PhoneNumber::normalize() when you use the send DTOs.
Provider routing (OTPIQ_DEFAULT_PROVIDER)
The provider field tells OTPIQ which channels to try and in what order. If you omit provider on SendVerificationData / SendCustomMessageData, the SDK uses config('otpiq.defaults.provider'), which comes from OTPIQ_DEFAULT_PROVIDER.
| Value | Behavior |
|---|---|
sms |
SMS only (carrier). |
whatsapp |
WhatsApp only. |
telegram |
Telegram only. |
whatsapp-sms |
WhatsApp first; if that fails, SMS. |
telegram-sms |
Telegram first; if that fails, SMS. |
whatsapp-telegram-sms |
WhatsApp → Telegram → SMS (widest reach; default in config). |
Override per message with the Provider enum:
use LeviLabs\LaravelOtpiq\Enums\Provider;
use LeviLabs\LaravelOtpiq\Data\Messaging\SendVerificationData;
OtpiqMessage::sendVerification(new SendVerificationData(
phoneNumber: '9647501234567',
verificationCode: '123456',
provider: Provider::WhatsAppSms, // ignores OTPIQ_DEFAULT_PROVIDER for this send
));
Reference: Send SMS API
Multiple OTPIQ projects
One Laravel application can send on behalf of several OTPIQ dashboard projects (production vs staging, white-label apps, etc.). Each project has its own API key and credit balance.
1. Add keys in config/otpiq.php:
'projects' => [
'default' => [
'api_key' => env('OTPIQ_API_KEY'),
'base_url' => env('OTPIQ_BASE_URL', 'https://api.otpiq.com/api'),
],
'staging' => [
'api_key' => env('OTPIQ_STAGING_API_KEY'),
'base_url' => env('OTPIQ_BASE_URL', 'https://api.otpiq.com/api'),
],
],
2. .env:
OTPIQ_PROJECT=default
OTPIQ_API_KEY=sk_live_prod_…
OTPIQ_STAGING_API_KEY=sk_dev_…
3. Pass the project key on any call:
OtpiqMessage::sendVerification($data, project: 'staging');
OtpiqMessage::track($smsId, project: 'staging');
OtpiqProject::info(project: 'default');
If the project key is missing or has no api_key, the SDK throws OtpiqProjectException.
API reference coverage
This package wraps the REST API documented at docs.otpiq.com/api-reference/introduction. Every endpoint listed in the official reference is supported:
| Docs section | HTTP | OTPIQ path | Laravel facade / method |
|---|---|---|---|
| Project | |||
| Get Project Info | GET |
/info |
OtpiqProject::info() |
| Messaging & SMS | |||
| Send SMS | POST |
/sms |
OtpiqMessage::sendVerification() · sendCustom() · sendWhatsAppTemplate() (same API, different smsType bodies) |
| Track SMS | GET |
/sms/track/{smsId} |
OtpiqMessage::track($smsId) |
| International SMS Pricing | GET |
/international-sms-pricing |
OtpiqPricing::internationalSmsPricing() |
| Sender ID | |||
| Get Sender IDs | GET |
/sender-ids |
OtpiqSenderId::list() |
| Delivery reports | |||
| Generate Delivery Report | POST |
/reports/generate |
OtpiqReport::generate($start, $end, …) |
| List Delivery Reports | GET |
/reports |
OtpiqReport::list() |
| List WhatsApp Resources | GET |
/whatsapp/resources |
OtpiqWhatsApp::list() |
| WhatsApp campaigns | OtpiqWhatsAppCampaign facade |
||
| Create Campaign | POST |
/whatsapp/campaigns |
OtpiqWhatsAppCampaign::create([…]) |
| Get Recipient Schema | GET |
/whatsapp/campaigns/recipient-payload |
OtpiqWhatsAppCampaign::recipientPayload() |
| Attach Recipients | POST |
/whatsapp/campaigns/{id}/recipients |
OtpiqWhatsAppCampaign::attachRecipients($id, […]) |
| Send Sample | POST |
/whatsapp/campaigns/{id}/sample |
OtpiqWhatsAppCampaign::sendSample($id, […]) |
| Start Campaign | POST |
/whatsapp/campaigns/{id}/start |
OtpiqWhatsAppCampaign::start($id) |
| List Campaigns | GET |
/whatsapp/campaigns |
OtpiqWhatsAppCampaign::list() |
| Get Campaign Details | GET |
/whatsapp/campaigns/{id} |
OtpiqWhatsAppCampaign::get($id) |
| Update Campaign | PATCH |
/whatsapp/campaigns/{id} |
OtpiqWhatsAppCampaign::update($id, […]) |
| Delete Campaign | DELETE |
/whatsapp/campaigns/{id} |
OtpiqWhatsAppCampaign::delete($id) |
Not HTTP endpoints (documented separately):
- SMS delivery webhooks — configured on each send via
deliveryReportorOTPIQ_WEBHOOK_*; handled in your app withDeliveryWebhook(how webhooks work). - WhatsApp inbound webhooks — configured in the OTPIQ dashboard; verify with
WhatsAppInboundWebhookVerifier(WhatsApp webhook).
Aliases in composer.json: OtpiqWhatsAppCampaign → LeviLabs\LaravelOtpiq\Facades\OtpiqWhatsAppCampaign.
Send messages
Verification OTP (most common)
use LeviLabs\LaravelOtpiq\Data\Messaging\SendVerificationData;
use LeviLabs\LaravelOtpiq\Facades\OtpiqMessage;
$response = OtpiqMessage::sendVerification(new SendVerificationData(
phoneNumber: '9647501234567',
verificationCode: '123456',
// provider: optional — defaults to OTPIQ_DEFAULT_PROVIDER
// senderId: optional — custom SMS sender ID when supported
));
// $response->smsId, $response->cost, $response->remainingCredit, $response->paymentType
Custom SMS / transactional text
Requires appropriate sender ID and OTPIQ product rules for non-OTP SMS:
use LeviLabs\LaravelOtpiq\Data\Messaging\SendCustomMessageData;
OtpiqMessage::sendCustom(new SendCustomMessageData(
phoneNumber: '9647501234567',
customMessage: 'Your order #1042 is confirmed.',
senderId: 'MyBrand',
));
WhatsApp template message
use LeviLabs\LaravelOtpiq\Data\Messaging\SendWhatsAppTemplateData;
use LeviLabs\LaravelOtpiq\Enums\Provider;
OtpiqMessage::sendWhatsAppTemplate(new SendWhatsAppTemplateData(
phoneNumber: '9647501234567',
templateName: 'verification_template',
whatsappAccountId: '68c46fecc509cdcec8fb3ef2',
whatsappPhoneId: '68da31fb518ac3db3eb0a0f4',
templateParameters: ['body' => ['1' => '123456']],
provider: Provider::WhatsApp,
));
List connected WhatsApp assets with OtpiqWhatsApp::list() (maps to List WhatsApp Resources in the API docs).
WhatsApp campaigns
Bulk template messaging uses the WhatsApp Campaigns API (campaign docs). Use the OtpiqWhatsAppCampaign facade (not OtpiqMessage).
Typical flow:
- Resources —
OtpiqWhatsApp::list()fortemplateId,phoneNumberId, etc. - Recipient schema —
OtpiqWhatsAppCampaign::recipientPayload(templateId: '…')orrecipientPayload(campaignId: '…'). - Create —
create()withname,templateId,phoneNumberId,scheduleType(noworschedule). - Attach recipients —
attachRecipients($campaignId, ['recipients' => […]]). - Start —
start($campaignId)(forscheduleType: nowdrafts). Scheduled campaigns start automatically atscheduledFor.
use LeviLabs\LaravelOtpiq\Facades\OtpiqWhatsApp;
use LeviLabs\LaravelOtpiq\Facades\OtpiqWhatsAppCampaign;
$resources = OtpiqWhatsApp::list();
$campaign = OtpiqWhatsAppCampaign::create([
'name' => 'Summer promo',
'templateId' => '664abc123def456789012345',
'phoneNumberId' => '664def456abc789012345678',
'scheduleType' => 'now',
]);
$id = $campaign['_id']; // MongoDB id from API
$schema = OtpiqWhatsAppCampaign::recipientPayload(templateId: '664abc123def456789012345');
OtpiqWhatsAppCampaign::attachRecipients($id, [
'recipients' => [
// shape must match $schema['exampleRecipient'] / fields from recipient-payload
],
]);
OtpiqWhatsAppCampaign::start($id);
// Optional: test before full send
OtpiqWhatsAppCampaign::sendSample($id, ['recipients' => [/* … */]]);
// Monitor
OtpiqWhatsAppCampaign::list(status: 'running');
OtpiqWhatsAppCampaign::get($id, page: 1, limit: 20);
Other campaign methods: update($id, …) (draft/scheduled only), delete($id) (draft/scheduled only). Pass ?string $project = null as the last argument on any call for multi-project setups.
Sender IDs, pricing & delivery reports
use LeviLabs\LaravelOtpiq\Facades\OtpiqPricing;
use LeviLabs\LaravelOtpiq\Facades\OtpiqReport;
use LeviLabs\LaravelOtpiq\Facades\OtpiqSenderId;
$senderIds = OtpiqSenderId::list();
$pricing = OtpiqPricing::internationalSmsPricing(page: 1, limit: 100, countryIso: 'IQ');
$reports = OtpiqReport::list(limit: 50, statuses: ['available']);
$newReport = OtpiqReport::generate(
'2026-01-01T00:00:00.000Z',
'2026-01-31T23:59:59.999Z',
);
// $newReport['downloadUrl'] — CSV download link from OTPIQ
Track delivery
Poll status when you do not rely solely on webhooks:
$tracking = OtpiqMessage::track($response->smsId);
// $tracking->status (pending, sent, delivered, read, failed)
// $tracking->isFinalStatus, $tracking->lastChannel, $tracking->channelFlow
Delivery webhooks
Configure OTPIQ_WEBHOOK_* or pass DeliveryReportConfig on a single send. OTPIQ POSTs JSON to your URL and sends X-OTPIQ-Webhook-Secret (docs).
OTPIQ_WEBHOOK_REPORT_TYPE:
| Value | Meaning |
|---|---|
final |
Webhooks only when the message reaches a terminal state (delivered or failed). |
all |
Webhooks for intermediate states too (e.g. sent). |
Example route handler:
use LeviLabs\LaravelOtpiq\Support\DeliveryWebhook;
$report = DeliveryWebhook::dispatchIfVerified(
payload: $request->all(),
receivedSecret: $request->header('X-OTPIQ-Webhook-Secret'),
expectedSecret: config('otpiq.delivery_report.webhook_secret'),
project: config('otpiq.default'),
);
if ($report === null) {
abort(401);
}
return response('OK');
Respond with 2xx within ~10 seconds. Use $report->smsId for idempotency.
WhatsApp inbound webhooks
Separate from SMS delivery webhooks. Configure per phone number in the OTPIQ dashboard. Verify with HMAC (X-OTPIQ-Webhook-Signature, X-OTPIQ-Webhook-Timestamp) using WhatsAppInboundWebhookVerifier — inbound webhook docs.
Facades & services
| Facade | Contract | Covers |
|---|---|---|
OtpiqProject |
OtpiqProjectServiceContract |
Project info |
OtpiqMessage |
OtpiqMessagingServiceContract |
Send SMS API + track |
OtpiqSenderId |
OtpiqSenderIdServiceContract |
Sender IDs |
OtpiqPricing |
OtpiqPricingServiceContract |
International SMS pricing |
OtpiqReport |
OtpiqReportServiceContract |
Delivery report CSVs |
OtpiqWhatsApp |
OtpiqWhatsAppResourceServiceContract |
WhatsApp resources |
OtpiqWhatsAppCampaign |
OtpiqWhatsAppCampaignServiceContract |
All 10 campaign endpoints |
Resolve contracts from the container if you prefer constructor injection.
Persistence & sync
otpiq_messages— upserted on send (MessageSent) and on webhook / track updates (DeliveryStatusUpdated).php artisan otpiq:sync-statuses— re-calls the track API for rows whereis_finalis false (missed webhooks).
php artisan otpiq:sync-statuses --limit=100
Testing
composer test
composer analyse
composer format
Security
See SECURITY.md. Never expose OTPIQ_API_KEY or webhook secrets in client-side code, logs, or public repositories.
License
MIT — see LICENSE.md.
OTPIQ is a commercial messaging service; billing and refunds follow OTPIQ’s terms and privacy policy.