Skip to content
Fintech

Turkish e-Invoicing with .NET: Integrating Hızlı Teknoloji e-Connect

e-Fatura vs e-Arşiv, SOAP clients that behave in modern .NET, UBL-TR mapping without tears, the status lifecycle, cancellation rules and a go-live checklist from real ERP integrations.

5 min read Updated Sep 3, 2026
Turkish e-Invoicing with .NET: Integrating Hızlı Teknoloji e-Connect

If you build software that issues invoices in Turkey, you don't get to choose whether to care about e-Fatura. Above the revenue thresholds, electronic invoicing through the Revenue Administration (GİB) is mandatory, the format is UBL-TR, and the practical route for almost everyone is a special integrator — a licensed provider whose API sits between your app and the government. Hızlı Teknoloji's e-Connect is one of the established players there, and this post is the integration guide I wish existed when I first wired an ERP into it: the .NET version, SOAP warts and all.

Flow: a .NET app calls e-Connect over SOAP, which handles e-Fatura and e-Arşiv delivery to GİB

The lay of the land in five minutes

Two document families matter for a typical business app:

  • e-Fatura — for customers who are themselves registered e-invoice users. Delivery happens over GİB's network, mailbox-to-mailbox; the invoice is "sent" when the system routes it, and B2B scenarios can involve accept/reject responses.
  • e-Arşiv — for everyone else (consumers, unregistered companies). The invoice is issued electronically, reported to GİB, and delivered to the customer by e-mail or a link.

Which one applies is per recipient, decided at issue time by checking the recipient's tax number against the registered-user list. Every integrator, e-Connect included, exposes a taxpayer-check operation for exactly this. Your invoice flow therefore always starts with a lookup — bake that into your design, not as an afterthought.

Talking SOAP from modern .NET

e-Connect's integration surface is SOAP web services with credential-based authentication (the ecosystem even has an open-source community client — a PHP/Laravel package on GitHub — which is a decent map of the operation names even if you live in C#). In .NET, resist the urge to hand-craft envelopes; generate a typed client:

# One-time: generate a WCF client from the service WSDL
dotnet tool install --global dotnet-svcutil
dotnet-svcutil https://efatura.example-endpoint.com.tr/Service?wsdl \
  --outputDir ./Generated --namespace "*,EConnect.Generated"

Then wrap the generated client behind your own interface. This is the single highest-value decision in the whole integration — your app should depend on IEInvoiceGateway, not on a WSDL's opinion of the world:

public interface IEInvoiceGateway
{
    Task<RecipientType> CheckTaxpayerAsync(string vknTckn, CancellationToken ct);
    Task<SendResult> SendInvoiceAsync(InvoiceDraft draft, CancellationToken ct);
    Task<InvoiceStatus> GetStatusAsync(string uuid, CancellationToken ct);
    Task<byte[]> DownloadPdfAsync(string uuid, CancellationToken ct);
}

public sealed class EConnectGateway(EConnectClientFactory factory, IOptions<EConnectOptions> opt)
    : IEInvoiceGateway
{
    public async Task<SendResult> SendInvoiceAsync(InvoiceDraft draft, CancellationToken ct)
    {
        using var client = factory.Create();          // fresh channel; WCF channels fault

        var request = InvoiceMapper.ToUblTr(draft, opt.Value.SenderAlias);
        var response = await client.SendInvoiceAsync(
            opt.Value.Username, opt.Value.Password, request);

        return response.Code == "0"
            ? SendResult.Accepted(response.Uuid, response.InvoiceNumber)
            : SendResult.Rejected(response.Code, response.Message);
    }
    // ... remaining operations follow the same shape
}

WCF-specific advice earned the annoying way: treat channels as disposable (a faulted channel poisons every later call), set explicit binding timeouts (the defaults are optimistic), and log the raw request/response XML on failures via an IClientMessageInspector — when the integrator's support asks "can you send us the envelope?", you want to say yes in one minute, not reproduce the bug in three days.

The invoice itself: UBL-TR without tears

UBL-TR is XML with strong opinions — mandatory scenario codes, profile IDs (TICARIFATURA, EARSIVFATURA…), line-level tax breakdowns that must sum perfectly. The integrator validates before GİB does, and rejection messages reference UBL paths. Two habits keep this civilized:

  1. One mapper, heavily unit-tested. Money and VAT rounding live in exactly one place. Test the ugly cases: discounts, withholding (tevkifat), multiple VAT rates on one invoice, currency invoices with exchange rates.
  2. Store what you sent. Persist the full UBL you submitted, the returned UUID and invoice number, and every status transition. e-invoices are legal documents; "we can regenerate it from the order" does not satisfy an auditor whose question is about the document that went out.

Status is a lifecycle, not a boolean

DRAFT → QUEUED → SENT ─┬→ DELIVERED (e-Fatura, routed via GİB)
                       │        └→ ACCEPTED / REJECTED (commercial profile)
                       └→ REPORTED (e-Arşiv) → EMAILED
        any state ──────→ ERROR (schema, mailbox, GİB outage…)

Poll status asynchronously (a Quartz.NET job every few minutes is a natural fit) and surface the state on your invoice screen. The failure modes worth explicit handling: recipient mailbox not found (their alias changed — re-check the taxpayer list), schema rejection (your mapper has a bug — alert loudly), and GİB maintenance windows (retry with patience; they happen, usually at month-end when everyone is invoicing).

Cancellation and its cousins

Here the two document types diverge sharply, and mixing them up creates accounting incidents: an e-Arşiv invoice can be cancelled through the integrator within its legal window; a basic-profile e-Fatura, once delivered, is effectively immutable — correction happens with a return invoice issued by the counterparty or a new corrective document. Encode these rules in your domain layer so the UI never offers a cancel button that the law doesn't.

A go-live checklist from the trenches

  • Full rehearsal on the integrator's test environment — including PDF retrieval and cancellation, not just the happy send.
  • Taxpayer check cached with a short TTL; the list changes, but not per-second.
  • Idempotency on send: key on your own invoice id so a retry after timeout can't create a duplicate legal document.
  • Credit/contour monitoring if your plan meters documents — running out of credits on the last day of the month is a classic.
  • Every envelope logged (masked where needed) to central logging with the invoice UUID as correlation id.

I've integrated Turkish e-invoice flows into ERPs and SaaS products enough times to have opinions about UBL rounding. If e-Fatura is between you and a launch, bring it over — this is solved-problem territory.

Keep reading

Related articles