vorgio-app / vorgio-php
Official PHP SDK for the Vorgio API. Issue invoices, send them to clients, and verify webhooks.
Requires
- php: ^8.2
- ext-hash: *
- ext-json: *
- guzzlehttp/guzzle: ^7.5
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.50
- illuminate/support: ^10.0 || ^11.0 || ^12.0 || ^13.0
- pestphp/pest: ^2.34 || ^3.0
- phpstan/phpstan: ^1.10 || ^2.0
- phpunit/phpunit: ^10.5 || ^11.0
README
Official PHP SDK for the Vorgio invoicing API.
Thin, framework-agnostic wrapper. Optional Laravel auto-discovery (service provider + facade + publishable config) loads automatically when Laravel is installed; in vanilla PHP / WordPress the SDK works without it.
- PHP 8.2+
- Zero retries / no caching — callers control retry policy explicitly
- HMAC-SHA256 webhook verification with replay protection
- Auto-generated UUIDv7 idempotency keys when you don't supply one
Installation
composer require vorgio-app/vorgio-php
Quick start
use Vorgio\VorgioClient; $vorgio = new VorgioClient( token: 'act_…', // from Vorgio → Team → API tokens baseUrl: 'https://vorgio.app', ); $result = $vorgio->checkouts()->create([ 'client' => [ 'external_id' => 'wc_customer_42', 'name' => 'Jane Customer', 'email' => 'jane@example.com', 'address' => 'Musterstraße 1', 'zip' => '10115', 'city' => 'Berlin', 'country' => 'DE', 'language' => 'de', 'rate' => 0, 'vat' => 19, 'default_position_mode' => 'fixed', ], 'invoice' => [ 'tax_rate' => 19, 'due_offset_days' => 14, 'positions' => [ [ 'id' => '0193f7b0-1b8a-7b7d-9ad0-0c7b5b1d5f3e', 'date' => '2026-05-10', 'mode' => 'fixed', 'description' => 'One unit of widget', 'amount_cents' => 9900, ], ], ], 'send' => [ 'subject' => 'Your invoice from Demo Shop', 'body' => "Hello,\n\nplease find your invoice attached.", ], 'metadata' => ['order_id' => 'wc_order_999'], // round-tripped on every webhook ]); echo $result['data']['invoice']['number']; // INV-2026-0042
See examples/checkout.php for a runnable script.
Resources
| Surface | Method |
|---|---|
POST /v1/checkouts |
$vorgio->checkouts()->create($body) |
GET /v1/invoices |
$vorgio->invoices()->list($query) |
POST /v1/invoices |
$vorgio->invoices()->create($body) |
GET /v1/invoices/{id} |
$vorgio->invoices()->retrieve($id) |
PATCH /v1/invoices/{id} |
$vorgio->invoices()->update($id, $body) |
DELETE /v1/invoices/{id} |
$vorgio->invoices()->delete($id) |
POST /v1/invoices/{id}/send |
$vorgio->invoices()->send($id, $body) |
POST /v1/invoices/{id}/mark-paid |
$vorgio->invoices()->markPaid($id, $body) |
GET /v1/invoices/{id}/pdf |
$vorgio->invoices()->pdf($id) |
GET /v1/clients |
$vorgio->clients()->list($query) |
POST /v1/clients |
$vorgio->clients()->create($body) |
GET /v1/clients/{id} |
$vorgio->clients()->retrieve($id) |
PATCH /v1/clients/{id} |
$vorgio->clients()->update($id, $body) |
DELETE /v1/clients/{id} |
$vorgio->clients()->delete($id) |
Every method returns the decoded JSON response as a PHP array. The full
contract for each request/response shape lives at
vorgio.app/api-reference.
Idempotency
POST / PATCH / PUT / DELETE requests automatically receive a UUIDv7
Idempotency-Key header when you don't provide one — that's the right
default for genuinely new requests. Pass an explicit key when you want safe
replays:
$vorgio->checkouts()->create($body, idempotencyKey: 'wc_order_'.$order->id);
If you ship the same idempotency key twice within the server's retention
window, you get back the original response with an
Idempotency-Replay: true header.
Downloading PDFs
$pdf = $vorgio->invoices()->pdf($invoiceId); file_put_contents("/tmp/{$invoiceId}.pdf", $pdf->bytes); $cachedEtag = $pdf->etag; // stash this alongside your record // Later — pass the stored ETag to short-circuit a re-render: $pdf = $vorgio->invoices()->pdf($invoiceId, ifNoneMatch: $cachedEtag); if ($pdf->notModified) { // 304 — your cached copy is still current. } else { // PDF changed — overwrite local copy and update $cachedEtag = $pdf->etag. }
The returned Vorgio\InvoicePdf is a tiny value object with bytes,
etag, and notModified. The ETag value is opaque — store and replay it
verbatim, including the surrounding quotes.
Errors
use Vorgio\Exception\VorgioApiException; use Vorgio\Exception\VorgioRateLimitedException; use Vorgio\Exception\VorgioValidationException; try { $vorgio->checkouts()->create($body); } catch (VorgioValidationException $e) { // 422 — $e->errors is the field => messages map } catch (VorgioRateLimitedException $e) { // 429 — $e->retryAfter is the seconds you should wait } catch (VorgioApiException $e) { // any other 4xx / 5xx — $e->statusCode, $e->problem (RFC 7807), // $e->requestId for log correlation }
The SDK never retries on its own; pick a strategy that fits your runtime (queue back-off, sync retry with jitter, etc.).
Verifying webhooks
use Vorgio\Webhooks; use Vorgio\Exception\VorgioSignatureException; try { $event = Webhooks::constructEvent( payload: file_get_contents('php://input'), sigHeader: $_SERVER['HTTP_VORGIO_SIGNATURE'] ?? '', secret: getenv('VORGIO_WEBHOOK_SECRET'), ); } catch (VorgioSignatureException $e) { http_response_code(400); return; } if ($event->type === 'invoice.paid') { // …fulfil the order }
The signature scheme matches the server byte-for-byte: HMAC-SHA256 over
<unix_ts>.<raw_body>, with a 5-minute default replay tolerance.
See examples/webhook.php.
Laravel
When the package is installed inside a Laravel app it auto-registers a service provider. Publish the config and set the env vars:
php artisan vendor:publish --tag=vorgio-config
VORGIO_TOKEN=act_… VORGIO_BASE_URL=https://vorgio.app VORGIO_WEBHOOK_SECRET=whsec_…
Then resolve the client wherever you need it:
use Vorgio\Laravel\Facades\Vorgio; Vorgio::checkouts()->create([...]);
…or inject Vorgio\VorgioClient via the container.
Custom HTTP client
For testing, observability middleware, or alternative HTTP libraries, pass
your own Guzzle-compatible Psr\Http\Client\ClientInterface (or, more
specifically, GuzzleHttp\ClientInterface):
use GuzzleHttp\Client; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; $mock = new MockHandler([new Response(201, [], '{"data":{}}')]); $http = new Client(['handler' => HandlerStack::create($mock)]); $vorgio = new VorgioClient(token: 'act_test', httpClient: $http);
Development
composer install vendor/bin/pest # run tests composer format # PHP-CS-Fixer composer stan # PHPStan
License
MIT — see LICENSE.