Turkish e-Invoice (e-Fatura) Integration: Architecture Guide
e-Invoice integration is the part of enterprise projects in Turkey that gets postponed and ends up delaying the whole project. We explain the e-Fatura, e-Arşiv and e-İrsaliye concepts, a reference architecture built on a private integrator's API, the critical points such as numbering and status tracking, and the pitfalls we see most often in the field.
In enterprise projects in Turkey, "we'll deal with e-invoicing later" is the sentence that delays the project by at least a month. The order, shipping and collection flows get designed; the invoice is postponed with "the integrator has an API, we'll connect to it". Then the numbering rule, VAT rounding, the eight-day acceptance process for commercial invoices, e-archive reporting and the integrator's maintenance window surface one by one.
This article explains the e-Fatura (e-Invoice), e-Arşiv (e-Archive) and e-İrsaliye (e-Waybill) concepts, the ways enterprise software can connect to the Turkish Revenue Administration (GİB) system, a reference architecture built on a private integrator's API, the critical points such as numbering and status tracking, and the pitfalls we see most often in the field. Mandatory scope (turnover thresholds, sectors) is updated every year, so confirm current thresholds with GİB announcements and your accountant; here we cover the architecture, not the regulation.
Concepts: which document, in which case?
Document | When | Flow | Technical note |
|---|---|---|---|
e-Fatura (e-Invoice) | The recipient is also a registered e-invoice taxpayer | Sender → GİB → recipient, via GİB mailboxes | In the basic scenario the recipient cannot respond; in the commercial scenario it can accept/reject within 8 days |
e-Arşiv (e-Archive) | The recipient is not a registered e-invoice taxpayer (consumer, unregistered company) | Sender → recipient (e-mail/SMS/portal); reported to GİB | No accept/reject; different cancellation and correction rules |
e-İrsaliye (e-Waybill) | Shipment of goods | Same infrastructure as e-Fatura; recipient response exists | Issued at shipment time; a number series independent of invoices |
The software's first question is the same for every invoice: is the recipient a registered e-invoice taxpayer? The answer comes from GİB's registered-user list (queried by tax id) and determines the document type. The list changes daily; it is cached in the system but refreshed before every invoice.
Three ways to connect to GİB
The GİB portal: Manual use; for very low-volume businesses. Not integrated with enterprise software.
Direct integration: Your own server talks to GİB directly; test processes, the fiscal-seal infrastructure and 24/7 availability are your responsibility. Not preferred outside very high-volume organizations with strong technical teams.
A private integrator: Sending, receiving, signing, reporting and archiving through the API (SOAP or REST) of a GİB-licensed service provider. Almost all enterprise projects take this route, and the architecture below is for this scenario.
Reference architecture
[Order / Shipment / Collection] business flow
│
▼
[Invoice creation — domain] lines, VAT, discounts, exchange rate, number allocation
│
▼
[UBL-TR generation — mapping layer] Invoice → UBL-TR 1.2 XML, schema validation
│
▼
[Outbox: documents to send] record + send request in the same transaction
│
▼
[Integrator adapter] signing/seal, sending, taxpayer lookup, status query
│ (timeout, retry, circuit breaker)
▼
[Status tracking — scheduled job] sent → reached GİB → delivered → accepted/rejected
│
▼
[Archive + accounting integration] XML/PDF storage, accounting entry, reconciliationThree design decisions keep this architecture standing:
The integrator sits behind an adapter. The application talks to its own interfaces such as
InvoiceSender,TaxpayerLookup,StatusQuery; the integrator's SOAP envelopes and field names stay in the adapter. Switching integrators means writing an adapter, not rewriting the project.Sending is asynchronous, through an outbox. The invoice record and the "to be sent" record are written in the same transaction; the sender job holds the queue during an integrator outage and retries when it's back. The user's screen doesn't depend on the integrator's response time.
Status is a state machine. An invoice isn't finished when it's created; every stage is recorded and reverse transitions (rejection, cancellation) follow rules.
public enum InvoiceStatus {
DRAFT, NUMBERED, QUEUED, AT_INTEGRATOR, REACHED_GIB,
DELIVERED, ACCEPTED, REJECTED, CANCELLED, ERROR
}
public interface IntegratorPort { // the application's own contract
boolean isEInvoiceTaxpayer(TaxId id);
SendResult send(Ettn ettn, byte[] ublXml, DocumentType type);
Optional<DocumentStatus> queryStatus(Ettn ettn);
List<IncomingDocument> inbox(Instant lastCheck);
}
@Scheduled(fixedDelay = 60_000)
void refreshStatuses() {
for (Invoice inv : invoices.inPendingStates(200)) { // AT_INTEGRATOR, REACHED_GIB, DELIVERED
integrator.queryStatus(inv.ettn()).ifPresent(s -> {
inv.transition(s); // rule-checked transition; exception if invalid
if (s.status() == REJECTED) events.publish(new InvoiceRejected(inv.number(), s.reason()));
});
}
}Critical points
Numbering
The invoice number has 16 characters: a three-character series (unit code), a four-digit year and a nine-digit sequence. By GİB rule it must be per-year, gapless and sequential within the series. That means the number is generated only at "save" time and at a single point (a database sequence or a locked counter table). Reserving a number at draft stage and then cancelling creates a gap; two application instances incrementing the same counter without a lock produce duplicates. Separate series are used for e-Fatura and e-Arşiv.
ETTN and idempotency
Every document's universal unique identifier, the ETTN (a UUID), is generated by the application and passed to the integrator. A second send with the same ETTN gets an "already received" answer from the integrator; this is the key that makes send retries safe. Regenerating the ETTN on every attempt means duplicate invoices.
VAT and rounding
In UBL-TR, line amounts, tax bases and totals appear separately and must be consistent in schema validation. VAT rounded per line and VAT calculated on the total can differ by a cent; which method is used is agreed with accounting up front and applied in one place. For foreign-currency invoices, the exchange rate, rate date and TRY equivalents are mandatory fields.
Rejection, cancellation and returns
In a commercial e-invoice the recipient can reject within eight days; rejection voids the invoice legally, and the REJECTED state in the system must reverse the accounting entry. There is no rejection in a basic e-invoice; correction is done with a return invoice. For e-Arşiv, cancellation and correction rules follow the GİB guide. All three flows must be designed; the problem we see most in the field is "we cancelled it, but it still stands at GİB".
Incoming invoices
The integration is not one-way: your suppliers' e-invoices land in the integrator's inbox. Pulling them, matching them to purchase orders, accepting/rejecting commercial invoices within the deadline and transferring them to accounting is a separate flow, and it often creates more work than sending.
The pitfalls we see most in the field
Calling the integrator's API directly from the service layer: The user waits during an outage; everything changes when the integrator changes.
Querying the taxpayer list without caching, or never refreshing it: The first exhausts the integrator's quota, the second produces the wrong document type.
Ending status tracking at "sent": An invoice that never reached GİB or was rejected goes unnoticed for weeks.
Time zones: Invoice date and time are in Turkish time; in systems storing UTC, a day shift creates numbering and period errors.
Using real tax ids in the test environment: Integrators' test environments use separate identities; a production taxpayer lookup must not mix into a test invoice.
Leaving the archive to the integrator without keeping your own copy: The retention obligation belongs to the taxpayer; a copy of the XML and PDF must be in your own storage.
Testing and reconciliation
Schema validation: Every generated UBL-TR is validated against the XSD and schematron rules before sending; a faulty document is caught before it reaches the integrator.
Contract tests: The integrator's real responses are recorded as stubs; an API change shows up in CI.
Daily reconciliation: The invoice list in the application is compared with the list pulled from the integrator; missing, extra and status mismatches are reported.
Frequently asked questions
How much work is switching integrators?
If the integrator is behind an adapter, a few weeks: a new adapter, identity/certificate setup, validation in the test environment, series and number continuity on switch day. In integrations embedded directly in the service layer, it can take months.
Who does the e-Arşiv reporting?
With a private integrator, the integrator prepares the daily e-Arşiv report and submits it to GİB; the application's job is to send the documents correctly and on time. With direct integration, reporting is your responsibility.
Should we generate the invoice as a PDF first?
No; the source document is the UBL-TR XML, the PDF is a visualization. The PDF is produced from the XML with the integrator's or the application's XSLT template; it must be derived from a single source so the two never disagree.
Conclusion
e-Invoice integration is not an API call; it is a subsystem made of numbering, document-type decisions, asynchronous sending, a state machine, rejection/cancellation flows, an inbox and an archive. A design that keeps the integrator behind an adapter, sends from an outbox and tracks every document's status withstands both regulatory changes and integrator outages.
To add e-invoice, e-archive and e-waybill integration to your existing application, or make a problematic integration reliable, see our integration and automation and enterprise application development services. The discovery call and first estimate are free.