السلام عليكم ورحمة الله وبركاتة
I’m working with Laravel and doing the second phase of the integration. I’m using CsrRequest from Salla/ZATCA to generate it and that part works fine — all of this is in the sandbox (test) environment. I want to obtain the csid and secret key, but I get an error when I try to use the compliance endpoint from the sandbox:
{
“code”: “Invalid-Request”,
“message”: “Invalid message body, body should contain compliance_request_id field only as json filed”
}
What the Request Jason You are posting to generate Csid.
You need to first generate compliance csid and secret using
https://gw-fatoora.zatca.gov.sa/e-invoicing/core/compliance
Provide OTP and you will receive in reponse a request id ,CSID and Secret which needs to be used in complaince check API
https://gw-fatoora.zatca.gov.sa/e-invoicing/core/compliance/invoices
once Compliance check done then use below
https://gw-fatoora.zatca.gov.sa/e-invoicing/core/production/csids
and pass first step request id in the payload to obtain
Secret and PCSID for PROD
I was using Salla/Zatca for generate csr for sandbox env but i m now using this function to generate csr
public function generateCSR(): array
{
try {
if (!is_dir(storage_path('app/private'))) {
mkdir(storage_path('app/private'), 0755, true);
}
$deviceSerial = '1-TST|2-TST|3-' . bin2hex(random_bytes(16));
*// Configure EC key with P-256 curve (CRITICAL for ZATCA)*
$config = \[
"digest_alg" => "sha256",
"private_key_type" => OPENSSL_KEYTYPE_EC,
"curve_name" => "prime256v1" *// P-256 / secp256r1*
\];
*// Generate private key*
$privateKey = openssl_pkey_new($config);
if (!$privateKey) {
throw new \\Exception('Failed to generate private key: ' . openssl_error_string());
}
*// Build DN (Distinguished Name) - ZATCA requirements*
*// UID MUST be in the main DN, not just in SAN*
$dn = \[
"C" => "SA",
"OU" => $this->business_category,
"O" => $this->organization_name ?: $this->seller_name,
"CN" => $this->organization_name ?: $this->seller_name,
"UID" => $this->tax_number, *// CRITICAL: UID must be here*
\];
*// Create temp config file for OpenSSL extensions*
$configContent = <<<EOT
\[req\]
distinguished_name = req_distinguished_name
req_extensions = v3_req
\[req_distinguished_name\]
\[v3_req\]
subjectAltName = @alt_names
extendedKeyUsage = 1.3.6.1.5.5.7.3.1
\[alt_names\]
dirName.1 = dir_sect
\[dir_sect\]
SN = {$deviceSerial}
UID = {$this->tax_number}
title = 1100
registeredAddress = {$this->registered_address}
businessCategory = {$this->business_category}
EOT;
$tempConfigPath = storage_path('app/private/openssl_temp.cnf');
file_put_contents($tempConfigPath, $configContent);
*// Generate CSR with extensions*
$csr = openssl_csr_new($dn, $privateKey, \[
'digest_alg' => 'sha256',
'config' => $tempConfigPath
\]);
*// Clean up temp config*
@unlink($tempConfigPath);
if (!$csr) {
throw new \\Exception('Failed to generate CSR: ' . openssl_error_string());
}
*// Export private key and CSR*
if (!openssl_pkey_export($privateKey, $privateKeyPem)) {
throw new \\Exception('Failed to export private key');
}
if (!openssl_csr_export($csr, $csrPem)) {
throw new \\Exception('Failed to export CSR');
}
*// Save files*
$privateKeyPath = storage_path('app/private/zatca_private_key.pem');
$csrPath = storage_path('app/private/zatca_csr.pem');
file_put_contents($privateKeyPath, $privateKeyPem);
file_put_contents($csrPath, $csrPem);
Log::info('CSR generated successfully with P-256 curve', \[
'device_serial' => $deviceSerial,
'tax_number' => $this->tax_number
\]);
*// Verify the CSR was created correctly*
$csrData = openssl_csr_get_subject($csrPem, false);
$publicKey = openssl_csr_get_public_key($csrPem);
$keyDetails = openssl_pkey_get_details($publicKey);
Log::info('CSR verification', \[
'subject' => $csrData,
'key_type' => $keyDetails\['type'\] === OPENSSL_KEYTYPE_EC ? 'EC' : 'Other',
'curve' => $keyDetails\['ec'\]\['curve_name'\] ?? 'Unknown'
\]);
return \[
'success' => true,
'csr_content' => $csrPem,
'message' => 'CSR generated successfully with P-256 curve',
'files' => \[
'private_key' => $privateKeyPath,
'csr' => $csrPath
\],
'verification' => \[
'subject' => $csrData,
'curve' => $keyDetails\['ec'\]\['curve_name'\] ?? 'Unknown'
\]
\];
} catch (\\Exception $e) {
Log::error('CSR generation failed', \[
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
\]);
return \[
'success' => false,
'error' => $e->getMessage()
\];
}
}
but when im trying to generate csid with
https://gw-fatoora.zatca.gov.sa/e-invoicing/simulation/compliance
i got invalid csr i don’t know why ?
this function
public function getComplianceCSID(string $otp): array
{
try {
if (!$this->hasValidCSR()) {
return \[
'success' => false,
'error' => 'No valid CSR found. Please generate CSR first.'
\];
}
$csrPath = storage_path('app/private/zatca_csr.pem');
$rawPem = file_get_contents($csrPath);
*// Clean CSR for ZATCA API - remove headers and all whitespace*
$csrContent = preg_replace('/-----BEGIN CERTIFICATE REQUEST-----/i', '', $rawPem);
$csrContent = preg_replace('/-----END CERTIFICATE REQUEST-----/i', '', $csrContent);
$csrContent = trim(preg_replace('/\\s+/', '', $csrContent));
*// Validate base64 payload strictly; sandbox rejects otherwise*
$decoded = base64_decode($csrContent, true);
if ($decoded === false || base64_encode($decoded) !== $csrContent) {
return \[
'success' => false,
'error' => 'CSR invalid: payload is not valid base64 content expected by ZATCA.'
\];
}
Log::info('Cleaned CSR length', \['length' => strlen($csrContent)\]);
$payload = \['csr' => $csrContent\];
Log::info('Requesting Compliance CSID from ZATCA Sandbox');
$response = Http::timeout(30)
->withHeaders(\[
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Accept-Version' => 'V2',
'Accept-Language' => 'en',
'OTP' => $otp
\])
->post($this->base_url . '/compliance', $payload);
Log::info('ZATCA Compliance CSID response', \[
'status' => $response->status(),
'response' => $response->json()
\]);
if ($response->successful()) {
$data = $response->json();
return \[
'success' => true,
'binarySecurityToken' => $data\['binarySecurityToken'\] ?? null,
'secret' => $data\['secret'\] ?? null,
'request_id' => $data\['requestID'\] ?? null,
'disposition' => $data\['disposition'\] ?? null
\];
}
$responseData = $response->json();
$error = $responseData\['message'\] ?? 'Failed to get Compliance CSID - HTTP ' . $response->status();
return \[
'success' => false,
'error' => $error,
'status_code' => $response->status(),
'response' => $responseData
\];
} catch (\\Exception $e) {
Log::error('Compliance CSID request failed', \[
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
\]);
return \[
'success' => false,
'error' => $e->getMessage()
\];
}
}
so i need help im working in local env not production
for laravel project:
composer require sevaske/zatca
php artisan zatca:install
usage:
php artisan zatca:generate-csr
php artisan zatca:compliance-certificate
php artisan zatca:production-certificate
more details GitHub - sevaske/zatca: Zatca laravel package
Hello taron,
Hope that you are well.
It’s nice to hear that you have used the Sevaske package for laravel. I was also trying with it, but finally I didn’t get the proper result.
If you have properly completed both Phase 1 and Phase 2, that is great. So I need some help from you about it, and this is very urgent.
Looking forward to your response.
Best regards,