Skip to Content
SDKsPHP

PHP

useyona/einvoice-php is the official SDK for PHP 8.1 and later. It is built on PSR-18, so it works with the HTTP client your project already has, and every array it returns has a documented shape for phpstan and your IDE.

Install

composer require useyona/einvoice-php

The SDK needs a PSR-18 HTTP client. If your project has none, composer require guzzlehttp/guzzle installs one that the SDK finds automatically.

Quick start

You need a sandbox key

Create one in the Yona app under API keys, with the invoicing preset; it starts with sk_test_. The SDK needs nothing else: no host and no environment setting, because the key chooses the sandbox or the live service.

<?php require 'vendor/autoload.php'; use Useyona\EInvoice\EInvoice; $client = new EInvoice(['api_key' => getenv('YONA_API_KEY')]); // 1. A buyer $buyer = $client->buyers->create([ 'name' => 'Acme Nigeria Ltd', 'taxId' => '12345678-0001', 'email' => 'accounts@acme.ng', 'partyType' => 'company', 'address' => ['line1' => '1 Marina', 'city' => 'Lagos', 'country' => 'NG'], ]); // 2. A saved item $item = $client->items->create([ 'name' => 'Laptop', 'itemType' => 'goods', 'hsnCode' => '8471.30', 'productCategory' => 'Machinery', 'unitCode' => 'EA', 'unitPriceMinor' => '45000000', 'currency' => 'NGN', 'taxCategory' => 'STANDARD_VAT', ]); // 3. A draft invoice $invoice = $client->invoices->create([ 'invoiceKind' => 'B2B', 'invoiceDate' => '2026-10-05', 'currency' => 'NGN', 'buyerId' => $buyer['id'], 'lineItems' => [['itemId' => $item['id'], 'quantity' => 2]], ]); // 4. Finalise it and report it to the tax authority $client->invoices->finalise($invoice['id']); $client->submissions->submit($invoice['id']); $status = $client->submissions->getStatus($invoice['id']);

Submitting is asynchronous: poll submissions->getStatus or listen for the invoice.accepted webhook. The states are explained in Invoice lifecycle.

Sandbox and live

An sk_test_ key works in the sandbox and an sk_live_ key in live, on the same host; $client->mode() tells you which. To make a deployment refuse a key of the wrong kind:

new EInvoice(['api_key' => $key, 'assert_mode' => 'live']);

Responses and pagination

Methods return the API’s answer as an array with the API’s field names ($invoice['invoiceNumber']). List methods return a page:

$page = $client->buyers->list(['limit' => 50]);

$page->data holds the buyers and $page->pagination carries total, page, pageSize, totalPages, hasNext and hasPrevious.

EInvoice::paginate walks every page for you:

$list = fn (array $q) => $client->buyers->list($q); foreach (EInvoice::paginate($list, ['limit' => 100]) as $buyer) { echo $buyer['name'], "\n"; }

Received invoices and issued history return a Paginated object with the items under $res->data['items']. PDF downloads return a BinaryResponse with data, contentType and fileName.

Errors

A refused request throws an exception chosen by status:

StatusException
400, 422ValidationException
401AuthenticationException
402InsufficientCreditsException
403PermissionException
404NotFoundException
409ConflictException
429RateLimitException
5xxServerException

All extend ApiException, which carries status, errorCode, errors (per field), requestId and retryAfter:

use Useyona\EInvoice\Exception\ApiException; use Useyona\EInvoice\Exception\ValidationException; try { $client->invoices->create($params); } catch (ValidationException $e) { foreach ($e->errors as $error) { echo $error->field, ': ', $error->message, "\n"; } } catch (ApiException $e) { echo $e->status, ' ', $e->errorCode, ' ', $e->requestId; }

Branch on errorCode, not on the message, and quote requestId when you write to support. Problems before the API answers are TimeoutException, ConnectionException and ConfigException; all SDK exceptions extend EInvoiceException. The codes are listed on each page of the API Reference.

Retries and idempotency

Reads, and writes that carry an Idempotency-Key, are retried automatically on network errors, timeouts, 408, 429 and 5xx, with backoff and respecting Retry-After. Other writes are never retried.

Where the API accepts an Idempotency-Key (creating an invoice, submitting, sending, downloading and so on) the SDK generates one per call, so a retry is never applied or charged twice. Pass your own to make a retry safe across restarts:

use Useyona\EInvoice\RequestOptions; $options = new RequestOptions(idempotencyKey: "order-{$orderId}"); $client->invoices->create($params, $options);

Tune the defaults with timeout and retry in the configuration, or per call with RequestOptions (headers, maxRetries).

Webhooks

In plain PHP:

use Useyona\EInvoice\Exception\WebhookException; use Useyona\EInvoice\Webhooks; $secret = getenv('YONA_WEBHOOK_SECRET'); try { $raw = file_get_contents('php://input'); $event = Webhooks::verifyWebhook($raw, $_SERVER, $secret); } catch (WebhookException $e) { http_response_code(400); exit; } if ($event['type'] === 'invoice.accepted') { // … }

With a PSR-7 request (Laravel, Symfony, Slim and the like), pass the request itself as the headers:

$event = Webhooks::verifyWebhook($request->getBody(), $request, $secret);

verifyWebhook checks the Yona-Signature header over the raw body, accepts either signature while a rotated secret overlaps, and returns the event. Always pass the raw body, and deduplicate on $event['id']. Webhooks::signWebhookPayload signs a payload the way Yona does, so you can test your handler locally.

Endpoints are created and their secrets rotated in the Yona app; the SDK can list and test endpoints and read and redeliver deliveries and events. See Webhooks for the events.

Configuration

new EInvoice([...]) accepts:

OptionPurpose
assert_mode'sandbox' or 'live': refuse a key of the other kind
timeouttimeout in seconds for the HTTP client the SDK creates (default 30)
retrymax_retries (2), base_delay (0.5 s), max_delay (8 s), max_retry_after (60 s)
headersheaders sent on every request
http_client, request_factory, stream_factoryyour own PSR-18 client and PSR-17 factories (proxy, TLS)
base_urlanother gateway, for local development only; the key decides sandbox or live

What it covers

Every module mirrors a tag of the API Reference, and the method names are the reference’s operation names in the casing of the language (issueCreditNote, getStatus). Every method takes an optional trailing ?RequestOptions $options; request and query arrays have shapes named after the operation (CreateInvoiceBody, ListInvoicesQuery) in Useyona\EInvoice\Generated\Types. Each method needs the capability shown on the key; the full_integration preset has them all.

ModuleWhat it doesReference
$client->invoices, $client->submissions, $client->output, $client->shareLinksCreate, edit and finalise drafts; submit, retry and track them; credit and debit notes; PDFs, sending and share linksInvoices
$client->buyersThe businesses and people you invoice, with tax-number checks and reachabilityBuyers
$client->itemsWhat you sell, with codes, units, prices and tax categoriesItems
$client->referenceHS codes, reference lists, tax-ID lookup and invoice validationReference data
$client->sellersYour organisation as the seller, read-onlySellers
$client->inboundInvoices, $client->issuedHistoryInvoices other businesses sent you, and your issued history retrieved from the authorityReceived invoices
$client->organization, $client->invoiceSettings, $client->taxConnectionYour organisation and its readiness, invoice settings, the tax connectionOrganizations, Tax connection
$client->billingCredits and usage, payments, statements and your subscription, in accounts, payments, sandbox, statements, subscriptions, transactionsBilling
$client->webhooksList and test endpoints; read and redeliver deliveries and events, in endpoints, deliveries, events, eventTypesWebhooks

Account management (users, roles, API keys, purchases, webhook endpoint settings) is done in the Yona app, not through the API key.

The source is on GitHub at Elyonar/einvoice-php , with runnable examples under examples/.

Next steps

  • Invoice lifecycle: the states a submission climbs through, and what each one means
  • Webhooks: the events, and how deliveries are retried
  • Going live: the checklist before you deploy a live key
  • API Reference: every field, status code and error code