Search by

ss7api / php-sdk

shshdjdjdhdhdjdj1-rgb

Official PHP SDK for the SS7API transactional Call, SMS, OTP and Webhooks API.

v1.0.0 2026-09-17 05:59 UTC

This package is auto-updated.

Last update: 2026-11-12 04:11:04 UTC


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_... or pk_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:create
  • calls:read
  • sms:create
  • sms:read
  • otp:create
  • otp:verify
  • balance:read
  • usage:read
  • webhooks: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.