vivaltodev / connect-php
PHP SDK for the Vivalto Connect API: SMS, PDF, images and webhooks
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.8
- guzzlehttp/psr7: ^2.6
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
Requires (Dev)
- illuminate/config: ^11.0 || ^12.0
- illuminate/container: ^11.0 || ^12.0
- illuminate/support: ^11.0 || ^12.0
- pestphp/pest: ^3.0
- phpoption/phpoption: ^1.10
- phpstan/phpstan: ^2.1
- vlucas/phpdotenv: ^5.8
Suggests
- illuminate/support: Required for the optional service provider and facade.
Provides
None
Conflicts
None
Replaces
None
README
PHP client for the Vivalto Connect API v1. It sends SMS, renders HTML to PDF or images, reads message and document history, manages webhook endpoints and verifies incoming webhook signatures.
- API documentation: https://connect.vivalto.no/docs
- PHP 8.2 or newer
- PSR-18 HTTP client and PSR-17 factories
- Guzzle is the default transport
Installation
composer require vivaltodev/connect-php
The package is published on Packagist as vivaltodev/connect-php. To develop
the SDK inside the Connect monorepo, add the package directory as a path
repository instead:
{
"repositories": [
{
"type": "path",
"url": "../connect/packages/php-sdk",
"options": {
"symlink": true
}
}
],
"require": {
"vivaltodev/connect-php": "^0.2"
}
}
Quick start
use Vivalto\Connect\Client; use Vivalto\Connect\Config; $client = new Client(new Config( baseUrl: 'https://connect.vivalto.no', apiKey: getenv('VIVALTO_CONNECT_API_KEY'), )); $message = $client->messages()->send( to: '+4790000000', from: 'Vivalto', text: 'Hei fra Vivalto Connect', reference: 'order-123', ); echo $message->id;
send() generates an Idempotency-Key with bin2hex(random_bytes(16)) and
reuses it for every retry of the same call. Pass idempotencyKey: to supply
your own, for example when a queue job may be retried by the queue worker:
$client->messages()->send( to: '+4790000000', from: 'Vivalto', text: 'Hei', idempotencyKey: 'order-123-sms-1', );
The optional arguments are class (standard, premium, secret),
reference and metadata.
Read a message
$message = $client->messages()->get('msg_abc123'); echo $message->status; // queued, sending, sent, delivered, failed, ... echo $message->deliveredAt; // ISO 8601 timestamp or null
List messages
$page = $client->messages()->list([ 'status' => 'delivered', 'recipient' => '+4790000000', 'page' => 2, 'per_page' => 50, ]); foreach ($page->items as $message) { echo $message->id; } echo $page->total;
Supported filters are status, reference, recipient, created_after,
created_before, per_page and page.
Documents
Render a PDF
$result = $client->documents()->render( html: '<h1>Hei fra Vivalto</h1>', options: [ 'format' => 'A4', 'orientation' => 'portrait', 'margins' => ['top' => 20, 'right' => 15, 'bottom' => 20, 'left' => 15], ], ); file_put_contents('dokument.pdf', $result->contents); echo $result->mimeType; // application/pdf echo $result->documentId; // doc_...
render() returns the binary file synchronously and works for both PDF and
images. Use renderToFile() to write it directly:
$client->documents()->renderToFile('dokument.pdf', '<h1>Hei</h1>');
Render an image
$result = $client->documents()->render( html: '<h1>Hei</h1>', options: [ 'type' => 'png', 'window_size' => ['width' => 1200, 'height' => 630], 'full_page' => true, ], ); file_put_contents('bilde.png', $result->contents);
Asynchronous rendering
$document = $client->documents()->create( html: '<h1>Stor rapport</h1>', options: ['format' => 'A4'], ); // Poll until the document is completed. $document = $client->documents()->get($document->id); if ($document->isCompleted()) { $pdf = $client->documents()->download($document->downloadUrl); }
List documents
$page = $client->documents()->list([ 'type' => 'pdf', 'status' => 'completed', 'per_page' => 25, ]); foreach ($page->items as $document) { echo $document->id.' '.$document->status; }
Supported filters are type, status, from, to, per_page and page.
Webhooks
$endpoint = $client->webhooks()->create( url: 'https://client.example.com/hooks/vivalto', events: ['message.delivered', 'message.failed'], description: 'Production', ); // Returned once. Store it now; the API never returns it again. $secret = $endpoint->secret; $endpoints = $client->webhooks()->list(); $deliveries = $client->webhooks()->deliveries($endpoint->id, page: 1, perPage: 20);
Verify an incoming webhook
The signature header has the form t=<timestamp>,v1=<hex>. The digest is
HMAC-SHA256 over <timestamp>.<raw body>.
use Vivalto\Connect\WebhookSignature; $payload = file_get_contents('php://input'); $header = $_SERVER['HTTP_X_VIVALTO_SIGNATURE'] ?? ''; if (! $client->verifyWebhook($payload, $header, $secret)) { http_response_code(400); exit; } // The payload is authentic and inside the 300 second tolerance window.
verifyWebhook() accepts an optional $now unix timestamp (useful in tests)
and a $tolerance in seconds. WebhookSignature::sign($payload, $secret, $timestamp)
is available for test fixtures.
Errors
Every non-2xx response is converted to an exception. All of them extend
Vivalto\Connect\Exceptions\ApiException and expose status, errorCode and
details.
| Exception | Status |
|---|---|
AuthenticationException |
401, 403 |
NotFoundException |
404 |
ValidationException |
422 |
RateLimitException |
429, with retryAfter |
ServerException |
5xx after retries are exhausted |
ApiException |
any other status |
TransportException |
PSR-18 transport failure |
use Vivalto\Connect\Exceptions\ValidationException; try { $client->messages()->send('+4790000000', 'Vivalto', 'Hei'); } catch (ValidationException $exception) { $errors = $exception->errors(); }
Retries
The client retries 429 and 5xx responses up to retries times, with
exponential backoff (100ms, 200ms, 400ms, and so on) plus up to 100ms of
jitter. Other 4xx responses are never retried. The Idempotency-Key header is
identical on every attempt, so a retry cannot create a duplicate message.
$client = new Client(new Config( baseUrl: 'https://connect.vivalto.no', apiKey: $apiKey, timeout: 30, retries: 3, ));
Custom PSR-18 client
use Vivalto\Connect\Client; use Vivalto\Connect\Config; $client = new Client(new Config( baseUrl: 'https://connect.vivalto.no', apiKey: $apiKey, httpClient: $psr18Client, requestFactory: $psr17RequestFactory, streamFactory: $psr17StreamFactory, ));
Framework integration
The package includes an optional service provider and facade for frameworks that support package discovery. They are not required; the client works standalone with any PSR-18 implementation.
Development
composer install
composer test
composer analyse
Versioning
The package follows semantic versioning. The current release line is 0.2.
License
This package is proprietary. composer.json marks it as proprietary.