Skip to content

ZATCA integration

Saudi Arabia's Zakat, Tax and Customs Authority (ZATCA) requires B2B and B2C invoices to be electronically cleared or reported via the Fatoora platform (ZATCA Phase 2). Invora handles the full lifecycle: CSR generation, compliance verification, production onboarding, invoice signing, QR-code embedding, and clearance/reporting submission.

This guide covers the tenant-side flow: onboarding your organization and tracking submissions. Enabling the ZATCA regulation and setting its base configuration for a tenant is an admin operation handled by the Invora team.

Base URL & auth

Environment Base URL
Production https://gateway.invora.app
Staging https://stg-gateway.invora.app
TOKEN=$(curl -s -X POST https://auth.invora.app/oauth/v2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET" \
  --data-urlencode "scope=openid urn:zitadel:iam:org:project:id:372376660185448530:aud urn:zitadel:iam:user:resourceowner" \
  | jq -r '.access_token')

ZATCA environments

Environment enum Purpose Validation
ZATCA_ENVIRONMENT_PHASE2_SANDBOX Development Simulated responses, no real validation
ZATCA_ENVIRONMENT_PHASE2_SIMULATION Pre-production Real ZATCA validation, no legal effect
ZATCA_ENVIRONMENT_PHASE2_PRODUCTION Live compliance Legally binding clearance/reporting

Onboarding

Prerequisites

  1. Register at the ZATCA Fatoora portal and generate a one-time password (OTP).
  2. Configure your organization's tax identity (legal name, VAT number, address) via the Settings self-party — these must match ZATCA's records.
  3. Ensure ZATCA is enabled for your tenant (contact Invora support).

Start onboarding

InitiateOnboarding returns a server-sent stream of progress events — one per onboarding step.

curl -N -X POST https://gateway.invora.app/api/v1/regulations/zatca/onboarding \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "otp": "123456", "environment": "ZATCA_ENVIRONMENT_PHASE2_SIMULATION" }'
Response (streamed events)
{"step": "ZATCA_ONBOARDING_STEP_CSR_GENERATION", "disposition": "ZATCA_DISPOSITION_ISSUED", "isFinal": false}
{"step": "ZATCA_ONBOARDING_STEP_COMPLIANCE_CSID", "disposition": "ZATCA_DISPOSITION_ISSUED", "isFinal": false}
{"step": "ZATCA_ONBOARDING_STEP_PRODUCTION_CSID", "disposition": "ZATCA_DISPOSITION_ISSUED", "isFinal": true}

The three steps run in sequence:

  1. CSR generation — Invora generates a Certificate Signing Request from your tax identity.
  2. Compliance CSID — ZATCA issues a compliance certificate; Invora submits a test invoice for validation.
  3. Production CSID — ZATCA issues the production certificate for live invoicing.

A failed step emits disposition: ZATCA_DISPOSITION_REJECTED (or _ERROR) with structured errors:

Response (failed step)
{
  "step": "ZATCA_ONBOARDING_STEP_COMPLIANCE_CSID",
  "disposition": "ZATCA_DISPOSITION_REJECTED",
  "message": "Compliance check failed",
  "errors": [
    { "code": "COMPLIANCE_CHECK_FAILED", "category": "COMPLIANCE", "message": "Seller address city is required" }
  ],
  "isFinal": true
}

Poll progress

If the client disconnected mid-stream, poll the current state:

curl -X GET https://gateway.invora.app/api/v1/regulations/zatca/onboarding/progress \
  -H "Authorization: Bearer $TOKEN"
Response
{
  "currentStep": "ZATCA_ONBOARDING_STEP_PRODUCTION_CSID",
  "lastDisposition": "ZATCA_DISPOSITION_ISSUED",
  "completed": true
}

Check integration status

curl -X GET https://gateway.invora.app/api/v1/regulations/zatca/integration-status \
  -H "Authorization: Bearer $TOKEN"
Response
{
  "onboarded": true,
  "environment": "ZATCA_ENVIRONMENT_PHASE2_SIMULATION",
  "details": {
    "complianceCsidExpiry": "2027-01-15T00:00:00Z",
    "productionCsidExpiry": "2027-01-15T00:00:00Z",
    "isCompliant": true
  }
}

Submitting invoices

Once onboarded, ZATCA runs automatically when you freeze a document — whether through the Simple surface or the Documents API. On freeze, Invora validates against ZATCA rules, canonicalizes and signs the XML with your CSID, generates the TLV-encoded QR code, and submits for clearance (B2B) or reporting (B2C).

Invoice Model Behavior
B2B (standard tax invoice) Pre-clearance Blocking — ZATCA must clear before the invoice is valid
B2C (simplified invoice) Reporting Async — reported after issuance

The model is chosen automatically from the invoice content; no per-call configuration is needed.

Check a document's regulation status

curl -X GET https://gateway.invora.app/api/v2/regulations/documents/INV-2026-001 \
  -H "Authorization: Bearer $TOKEN"
Response
{
  "items": [
    {
      "regulationId": "zatca",
      "submissionStatus": "REGULATION_SUBMISSION_STATUS_ACCEPTED",
      "artifacts": [ { "artifactId": "signed-xml" }, { "artifactId": "qr-code" } ]
    }
  ]
}

This returns status and artifact descriptors (no bytes). For the typed ZATCA detail (clearance ID, invoice hash), read the regulation metadata on the document itself.

Download the signed XML or QR code

curl -X GET "https://gateway.invora.app/api/v2/regulations/zatca/documents/INV-2026-001/artifact?artifactId=signed-xml" \
  -H "Authorization: Bearer $TOKEN"
Response
{
  "artifact": "PD94bWwgdmVyc2lvbj0i...",
  "contentType": "application/xml",
  "artifactHash": "9f2c1a44..."
}

artifact is base64-encoded. Use artifactId=qr-code for the QR image.

Retry a failed submission

curl -X POST https://gateway.invora.app/api/v2/regulations/zatca/submissions/INV-2026-001/retry \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'

Decode a QR code

Decode a ZATCA QR code from a printed or received invoice into structured TLV data (seller, VAT number, totals, signature):

curl -X POST https://gateway.invora.app/api/v1/regulations/zatca/explain-qr \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "input": ["AXZWYW..."] }'

Common issues

Symptom Cause Fix
OTP expired OTP is short-lived Generate a fresh OTP from the Fatoora portal
Compliance CSID rejected Seller details don't match ZATCA records Correct the self-party legal name, VAT number, and address
Clearance rejected Calculation errors or missing fields Read errors/diagnostics, fix the document, retry
QR code invalid Document altered after signing Frozen documents are immutable — investigate corruption