This is a simple PHP library to work with the ZATCA API. You can send invoice data and manage certificates easily.
If you’re looking for a library to generate XML invoices, you can use this one: https://github.com/sevaske/php-zatca-xml
- Full coverage of ZATCA API endpoints (reporting, clearance, compliance)
- Authentication via certificate and secret or auth token
- Supports middleware for request/response processing
- Typed response objects for easy validation and error handling
- Supports multiple environments: sandbox, simulation, production
- Follows PSR standards (PSR-4, PSR-7, PSR-17, PSR-18)
- Works with any PSR-18 compatible HTTP client (e.g., Guzzle)
composer require sevaske/zatca-api:^2.0Create HTTP client and factories for PSR-17 / PSR-18. For example, GuzzleHttp
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use Sevaske\ZatcaApi\ZatcaClient;
$httpClient = new Client();
$factory = new HttpFactory();
// Initialize ZatcaClient with sandbox environment
$client = new ZatcaClient(
$httpClient,
$factory, // RequestFactoryInterface
$factory, // StreamFactoryInterface
'sandbox' // environment: sandbox | simulation | production
);use Sevaske\ZatcaApi\Exceptions\ZatcaRequestException;
use Sevaske\ZatcaApi\Exceptions\ZatcaResponseException;
try {
/**
* @var $client \Sevaske\ZatcaApi\ZatcaClient
*/
$certificateResponse = $client->complianceCertificate('your .csr file content', '112233');
} catch (ZatcaRequestException|ZatcaResponseException $e) {
// handle
}Create AuthToken from compliance certificate to make authorized requests.
/**
* @var $certificateResponse \Sevaske\ZatcaApi\Responses\CertificateResponse
* @var $client \Sevaske\ZatcaApi\ZatcaClient
*/
$authToken = new ZatcaAuth($certificateResponse->certificate(), $certificateResponse->secret());
$client->setAuthToken($authToken);Once you have a valid compliance certificate and auth token, you can submit invoices in the simulation environment.
Submitting 6 documents is required to switch to production mode.
use Sevaske\ZatcaApi\Exceptions\ZatcaRequestException;
use Sevaske\ZatcaApi\Exceptions\ZatcaResponseException;
try {
// B2P
$client->reportingInvoice('b2p invoice xml', 'hash', 'uuid');
$client->reportingInvoice('b2p debit note xml', 'hash', 'uuid');
$client->reportingInvoice('b2p credit note xml', 'hash', 'uuid');
// B2B
$client->clearanceInvoice('b2b invoice xml', 'hash', 'uuid');
$client->clearanceInvoice('b2b debit note xml', 'hash', 'uuid');
$client->clearanceInvoice('b2b credit note xml', 'hash', 'uuid');
} catch (ZatcaRequestException|ZatcaResponseException $e) {
// handle
}After submitting the required simulation invoices, you can request a production certificate.
This certificate allows you to submit real invoices in the production environment.
use Sevaske\ZatcaApi\Exceptions\ZatcaRequestException;
use Sevaske\ZatcaApi\Exceptions\ZatcaResponseException;
/**
* @var $client \Sevaske\ZatcaApi\ZatcaClient
*/
try {
$productionCertificateResponse = $client->productionCertificate($certificateResponse->requestId());
} catch (ZatcaRequestException|ZatcaResponseException $e) {
// handle
}Once the client is configured with the production certificate and environment, you can submit real invoices to ZATCA.
use Sevaske\ZatcaApi\ZatcaAuth;
use Sevaske\ZatcaApi\Exceptions\ZatcaRequestException;
use Sevaske\ZatcaApi\Exceptions\ZatcaResponseException;
/**
* @var $client \Sevaske\ZatcaApi\ZatcaClient
* @var $productionCertificateResponse \Sevaske\ZatcaApi\Responses\ProductionCertificateResponse
*/
$productionClient = $client->withEnvironment('production');
$productionAuth = ZatcaAuth($productionCertificateResponse->certificate(), $productionCertificateResponse->secret());
$productionClient->setAuthToken($productionAuth);
try {
// submitting production invoices
$productionClient->reportingInvoice('my real B2P invoice xml', 'hash', 'uuid');
$productionClient->clearanceInvoice('my real B2P invoice xml', 'hash', 'uuid');
} catch (ZatcaRequestException|ZatcaResponseException $e) {
// handle
}Middleware in ZatcaClient allows you to inspect, modify, or wrap HTTP requests and responses. It works as a pipeline, meaning that multiple middleware can be chained together, each receiving the request and a $next callable that continues to the next middleware and ultimately to the HTTP client.
ZatcaClient provides four ways to manage middleware:
withMiddleware($middleware)– returns a new cloned instance with the provided middleware. Existing middleware in the original client is replaced in the clone.setMiddleware($middleware)– mutates the current instance, replacing its middleware with the given ones.attachMiddleware($middleware)– mutates the current instance, adding the given middleware to the end of the existing middleware stack.withoutMiddleware- returns a new cloned instance with no middleware attached.
All middleware must implement the MiddlewareInterface:
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
interface MiddlewareInterface
{
/**
* @param RequestInterface $request The incoming request
* @param callable $next Callable to forward the request to the next middleware or the HTTP client
* @return ResponseInterface
*/
public function handle(RequestInterface $request, callable $next): ResponseInterface;
}For example, implementation of "logging" requests and responses:
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Sevaske\ZatcaApi\Interfaces\MiddlewareInterface;
// Attach a custom middleware to inspect requests and responses
$client = $client->withMiddleware(new class implements MiddlewareInterface
{
public function handle(\Psr\Http\Message\RequestInterface $request, callable $next): ResponseInterface
{
// request
$this->info('URL: ');
$this->info((string) $request->getUri());
$this->info('Body: ');
$this->info($this->safeStreamContents($request->getBody()));
/**
* @var $response \Psr\Http\Message\ResponseInterface
*/
$response = $next($request);
// response
$this->info('Response:');
$this->info($this->safeStreamContents($response->getBody()));
return $response;
}
private function safeStreamContents(\Psr\Http\Message\StreamInterface $stream): string
{
if (! $stream->isSeekable()) {
return '[unseekable stream]';
}
// Save original cursor position
$pos = $stream->tell();
// Read from beginning
$stream->rewind();
$content = $stream->getContents();
// Restore original cursor
$stream->seek($pos);
return $content;
}
private function info(string $text): void
{
echo "\n\r".$text;
}
});The library throws the following exceptions which you can catch and handle:
ZatcaException— general exception classZatcaRequestException— errors during the HTTP requestZatcaResponseException— errors processing the API response