ss7api / php-sdk
Official PHP SDK for the SS7API transactional Call, SMS, OTP and Webhooks API.
Requires
- php: >=8.1
- ext-curl: *
- ext-json: *
Requires (Dev)
None
Suggests
- illuminate/support: Required only for Laravel auto-discovery and facade integration.
Provides
None
Conflicts
None
Replaces
None
README
Official PHP SDK for the SS7API transactional Call, SMS, OTP and Webhooks API.
The SDK targets the live SS7API v1 contract at:
https://ss7api.com/api/v1
It is designed for server-side PHP applications including plain PHP, Laravel, CodeIgniter, Symfony and Composer-managed WordPress/WooCommerce projects.
Keep service API keys on the server. Never expose
pk_live_...orpk_test_...keys in browser JavaScript or public repositories.
Requirements
- PHP 8.1+
- cURL extension
- JSON extension
- Composer
The runtime SDK has no third-party package dependency.
Install
composer require ss7api/php-sdk
Quick start
<?php require __DIR__ . '/vendor/autoload.php'; use SS7Api\Client; use SS7Api\Enums\Purpose; $client = new Client(getenv('SS7API_API_KEY')); $sms = $client->sms()->send([ 'to' => '+8801XXXXXXXXX', 'message' => 'Your order has been confirmed.', 'purpose' => Purpose::ORDER_CONFIRMATION->value, 'external_reference' => 'order-123', ], 'order-123-confirmation'); print_r($sms);
Create operations are asynchronous. Call/SMS create endpoints return 202 with status=queued; use the returned id to query status.
Authentication and scopes
Authentication uses:
Authorization: Bearer SERVICE_API_KEY
The live API currently enforces these scopes:
calls:createcalls:readsms:createsms:readotp:createotp:verifybalance:readusage:readwebhooks:manage
The SDK does not create or store service API keys. Create/revoke keys in SS7API and inject them into the application through server-side environment/configuration.
Transactional purposes
The public transactional API accepts only these purposes:
otp
order_confirmation
payment_notice
appointment_reminder
delivery_update
service_notification
security_alert
other_transactional
Use SS7Api\Enums\Purpose to avoid string typos.
SMS
$sms = $client->sms()->send([ 'to' => '+8801XXXXXXXXX', 'message' => 'আপনার অর্ডার নিশ্চিত করা হয়েছে।', 'purpose' => Purpose::ORDER_CONFIRMATION->value, 'external_reference' => 'order-123', ], 'order-123-sms');
Check status:
$status = $client->sms()->get($sms['id']);
Possible normalized SMS states currently include:
queued, assigned, sending, sent, delivered, failed, expired, cancelled
The SDK sends JSON as UTF-8 with Unicode preserved.
Voice calls
$call = $client->calls()->create([ 'to' => '+8801XXXXXXXXX', 'text' => 'Your delivery is arriving today.', 'language' => 'en', 'purpose' => Purpose::DELIVERY_UPDATE->value, 'external_reference' => 'delivery-123', ], 'delivery-123-call');
Check status:
$status = $client->calls()->get($call['id']);
Possible normalized call states currently include:
queued, assigned, dialing, ringing, answered, completed,
busy, no_answer, failed, expired, cancelled
DTMF
DTMF is opt-in and plan-entitlement controlled.
$call = $client->calls()->create([ 'to' => '+8801XXXXXXXXX', 'text' => 'Enter the 8 digit code sent to your phone.', 'language' => 'bn-BD', 'purpose' => Purpose::SECURITY_ALERT->value, 'external_reference' => 'login-123', 'dtmf' => [ 'expected_length' => 8, 'min_length' => 8, 'max_length' => 8, 'allowed_digits' => '0123456789', 'terminator' => '#', 'inter_digit_timeout_ms' => 10000, 'overall_timeout_ms' => 120000, 'include_terminator' => false, 'hangup_on_collection_complete' => true, 'response_map' => [ '12345678' => 'confirmed', ], ], ], 'login-123-dtmf');
A completed status response may contain:
[
'dtmf' => [
'digits' => '12345678',
'completion_reason' => 'EXPECTED_LENGTH_REACHED',
'collected_at' => '...',
'result' => 'confirmed', // only when response_map matches
],
]
response_map is server-side semantic metadata. It is not forwarded to the physical gateway/provider.
OTP
Production OTP delivery currently supports the sms channel.
$otp = $client->otp()->send([ 'to' => '+8801XXXXXXXXX', 'channel' => 'sms', 'external_reference' => 'login-123', 'language' => 'bn', ], 'login-123-otp');
Verify:
$result = $client->otp()->verify($otp['id'], '123456'); if ($result['verified']) { // Continue the authenticated business flow. }
There is currently no public GET /otp/{id} endpoint. Do not invent/poll one.
A test API key can create sandbox OTP requests without a physical send/charge. The API response can include test_mode=true and a test_code. Test keys cannot place physical calls or ordinary SMS.
Balance
$balance = $client->account()->balance();
Example shape:
[
'balance' => '10.00',
'reserved' => '0.50',
'available' => '9.50',
'currency' => 'USD',
'updated_at' => '...',
]
Usage
Default reporting period is the current month through now.
$usage = $client->account()->usage();
Or provide a bounded range (server maximum: 90 days):
$usage = $client->account()->usage( new DateTimeImmutable('2026-09-01'), new DateTimeImmutable('2026-09-17') );
The response includes call/SMS/OTP counts, wallet credits used, pending reservations and current active-plan included/overage usage.
Idempotency
For every business operation that may be retried, send a stable Idempotency-Key.
$client->sms()->send($payload, 'order-9281-confirmation');
The same key + same logical request replays the original response. The same key + different request body returns HTTP 409 with idempotency_key_reused.
For operations that do not already have a stable business reference:
use SS7Api\Support\Idempotency; $key = Idempotency::uuid();
Webhook endpoint management
Create an endpoint:
use SS7Api\Enums\WebhookEnvironment; use SS7Api\Enums\WebhookEvent; $endpoint = $client->webhooks()->create([ 'name' => 'Production backend', 'url' => 'https://example.com/webhooks/ss7api', 'environment' => WebhookEnvironment::LIVE->value, 'events' => [ WebhookEvent::CALL_COMPLETED->value, WebhookEvent::SMS_SENT->value, WebhookEvent::OTP_VERIFIED->value, ], 'enabled' => true, ]); // The full signing secret is returned only on create/rotate. $secret = $endpoint['secret'];
Other operations:
$client->webhooks()->all(); $client->webhooks()->get($webhookId); $client->webhooks()->update($webhookId, ['enabled' => false]); $client->webhooks()->rotateSecret($webhookId); $client->webhooks()->test($webhookId); $client->webhooks()->deliveries($webhookId); $client->webhooks()->retryDelivery($deliveryId); $client->webhooks()->delete($webhookId); // soft-disables on the API
Webhook environments accepted by the live controller are live and test.
Verify incoming webhooks
SS7API signs the exact raw request body with HMAC-SHA256.
Headers:
X-SS7API-Event-Id
X-SS7API-Timestamp
X-SS7API-Signature: v1=<hex-hmac>
Signing input:
<timestamp>.<raw_request_body>
Verify before parsing or applying side effects:
use SS7Api\Webhook\WebhookVerifier; $rawBody = file_get_contents('php://input') ?: ''; $event = WebhookVerifier::verify( rawBody: $rawBody, signature: $_SERVER['HTTP_X_SS7API_SIGNATURE'] ?? '', timestamp: $_SERVER['HTTP_X_SS7API_TIMESTAMP'] ?? '', secret: getenv('SS7API_WEBHOOK_SECRET'), toleranceSeconds: 300, ); // At-least-once delivery: dedupe business side effects by event_id. $eventId = $event['event_id'];
Webhook envelopes have this shape:
{
"event_id": "...",
"type": "sms.sent",
"created_at": "...",
"livemode": true,
"api_version": "2026-08-27",
"data": {}
}
Error handling
use SS7Api\Exceptions\AuthenticationException; use SS7Api\Exceptions\ConflictException; use SS7Api\Exceptions\ForbiddenException; use SS7Api\Exceptions\InsufficientCreditException; use SS7Api\Exceptions\RateLimitException; use SS7Api\Exceptions\ValidationException; try { $client->sms()->send($payload, 'order-123'); } catch (AuthenticationException $e) { // Invalid, expired or inactive API key. } catch (InsufficientCreditException $e) { // HTTP 402. } catch (ForbiddenException $e) { // Missing scope, plan restriction, KYC gate, etc. } catch (ConflictException $e) { // Example: idempotency_key_reused. } catch (ValidationException $e) { print_r($e->errors()); } catch (RateLimitException $e) { $retryAfter = $e->retryAfterSeconds(); }
All API exceptions expose:
$e->statusCode(); $e->errorCode(); $e->responseData(); $e->responseHeaders();
The SDK normalizes both SS7API's transactional error envelope and Laravel validation responses.
Laravel
Package auto-discovery registers SS7Api\Client and the facade.
Add to .env:
SS7API_API_KEY=pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx SS7API_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxx
Use dependency injection:
use SS7Api\Client; public function send(Client $ss7api) { return $ss7api->sms()->send([...]); }
Or the facade:
use SS7Api\Laravel\Facades\SS7Api; SS7Api::sms()->send([...]);
Optional config publish:
php artisan vendor:publish --tag=ss7api-config
WordPress / WooCommerce
The core SDK works in Composer-managed WordPress projects. Keep the key server-side and initialize SS7Api\Client inside your plugin/backend code. See examples/wordpress/send-sms.php.
A dedicated WordPress/WooCommerce plugin can be built on top of this SDK without duplicating API logic.
Custom API base URL
Useful for staging/self-hosted test environments:
$client = new Client( apiKey: getenv('SS7API_API_KEY'), baseUrl: 'https://staging.example.com/api/v1', );
Tests
composer test
composer lint
The included test suite is offline and does not send real SMS/calls.
Source contract
This release was built against the live application source exported on 2026-09-17. Runtime controllers/services/tests were treated as authoritative where generated OpenAPI comments were stale. See docs/API-CONTRACT.md and docs/SOURCE-AUDIT.md.
License
MIT. See LICENSE.