User guide
How to use Egypt ETA e-Invoice, screen by screen.
The one thing to understand first
ETA answers a submission with HTTP 202. That means received, not filed.
The Authority validates afterwards, and a document can still come back Invalid. So this module never marks an invoice as filed on a 202. It records Submitted and polls until ETA returns Valid, Invalid or Rejected, showing the rejection reasons in the Authority's own words.
Any integration that treats 202 as success will eventually tell an accountant a tax document is lodged when it is not. That is the failure this module is built to avoid, and it is why the states look more cautious than you might expect.
The flow
posted invoice -> build ETA document v1.0
-> validate locally (free to fail)
-> sign via the local eSeal service (CAdES-BES)
-> submit -> HTTP 202 + document UUID
-> poll -> Valid / Invalid / RejectedBuilding
Post the invoice, then Build the ETA document from it.
Building runs local validation first, and local validation is free to fail. It catches what ETA would reject anyway, before anything leaves your server:
- a product with no ETA item code;
- a unit of measure with no unit code;
- a business receiver with no tax registration number;
- totals that disagree with the lines.
Fix and rebuild. Nothing has been sent.
Looking at the document
The Document tab holds the generated JSON exactly as it will be submitted. Restricted to an ETA Administrator, because it contains the whole document.
It is worth looking at the first few times. It is the thing the Authority actually judges, and reading one makes every subsequent rejection message legible.
Submitting
An accountant presses the button. Filing is never automatic.
A filed document cannot be unfiled, and a module that submitted on validation would file every draft mistake somebody posted — at which point the fix is a credit note and a conversation.
On success you get HTTP 202, a submissionUUID and a document uuid, all recorded. The state becomes Submitted.
Waiting for the real answer
The scheduled job polls until ETA gives a final status:
| Status | Meaning |
|---|---|
| Valid | Filed. This is the one that counts. |
| Invalid | ETA validated it and refused it |
| Rejected | Refused, with reasons |
Rejection detail is shown verbatim, in ETA's phrasing rather than paraphrased. Paraphrasing a tax authority's error message is how you end up searching for a string that does not exist in their documentation.
longId is captured too — that is what a QR code encodes, though this module does not render one yet.
Fixing a rejection
Read the reasons, correct the invoice, and build a new document. The rejected one keeps its record: what was sent, when, and why it was refused.
That history is worth having. A rejection you cannot reconstruct is a rejection you will hit again.
Credit notes and debit notes
Both are supported at document version 1.0, and follow the same path as an invoice.
What is not covered
- Vendor documents and export documents. Not built yet.
- Document versions other than 1.0. Others exist and are not claimed.
- QR rendering on the PDF. longId is captured; the report is not extended.
- POS receipts. That is technovision_eta_ereceipt, a separate module — a receipt is not an invoice with a new label.
Verification status
No call has ever been made to the Egyptian Tax Authority from this module. There is no ETA-issued Client ID or eSeal certificate on the development account, so nothing has been submitted to a real ETA environment — not even the pre-production one.
The automated suite runs against a mock ETA service: a real HTTP server, not a patched library, so a 202 carrying rejections, an expired token and a timeout are all exercised faithfully. It covers authentication, token refresh before expiry, the document structure against the published schema, local validation, accepted and rejected submissions, duplicate submission, status polling and unrecognised statuses.
Live sandbox validation needs ETA credentials and an eSeal certificate. It is a separate step, it is not covered by the suite, and it is not claimed.