Skip to content

Electronic invoicing with C#

This tutorial builds three simple C# console applications from scratch:

  1. Receive: connects and authenticates with the Invoicetronic API and download any new incoming invoices.
  2. Send: connects and authenticates with the Invoicetronic API and sends an invoice to the SDI.
  3. Update: connects and authenticates with the Invoicetronic API and consults the history of notifications returned by the SDI.

Before continuing, make sure all the prerequisites below are met.

Prerequisites

We assume that these prerequisites are met:

We use the dotnet tool and VS Code as they are available on most systems, but you can follow along with your favourite IDE (Visual Studio, Rider, etc.) if you prefer.

Tip

For an optimal C# experience in VS Code, you want to ensure that the C# Dev Kit extension is installed and enabled. For more information, see Getting Started with C# in VS Code.

Did you know?

We are the authors and maintainers of the open-source FatturaElettronica.NET package. If you are looking for a library to read, write, serialize, de-serialize, and validate invoices, then make sure to check it out.

Receive

Create the app

The first step is to create the application:

dotnet new console -n receive
The command created a new C# project named "quickstart" in a directory with the same name. Step into that directory:

cd receive

Install the SDK

Once in the quickstart directory, install the C# SDK:

 dotnet add package Invoicetronic.Sdk

Once that's done, open VS Code in the current directory:

code .

Click on the Program.cs file to see its contents in VS Code right pane.

Configure the SDK

Replace Program.cs default content with the following:

Configure the SDK
using Invoicetronic.Sdk.Api;
using Invoicetronic.Sdk.Client;
using static Invoicetronic.Sdk.Model.Receive;

// Configure the SDK.
var config = new Configuration
{
    BasePath = "https://api.invoicetronic.com/v1",
    Username = "YOUR TEST API KEY (starts with ik_test_)"
};

As you can see, we initialize a Configuration instance by setting the API's base path and your test API Key (not the live one). Notice how we use the Username property to set the API Key.

API Key comes in pairs

When you create your account, you obtain a pair of API Keys. One is the test key for the API Sandbox, and the other is the live API's. You can tell the difference because the former starts with ik_test_, while the latter begins with ik_live_. In this tutorial, always use the test key.

Download invoices

We are ready to make a request. We want to download new vendor invoices that may be avaiable from the SDI. Add these lines:

Download unread invoices
// Download unread invoices.
var receiveApi = new ReceiveApi(config);

try
{
    var inboundInvoices = await receiveApi.ReceiveGetAsync(unread:true, includePayload:true);
    Console.WriteLine($"Received {inboundInvoices.Count} invoices");

    foreach (var invoice in inboundInvoices)
    {
        switch (invoice.Encoding)
        {
            case EncodingEnum.Xml:
                File.WriteAllText(invoice.FileName, invoice.Payload);
                break;
            case EncodingEnum.Base64:
                File.WriteAllBytes(invoice.FileName, Convert.FromBase64String(invoice.Payload));
                break;
        }

        Console.WriteLine($"Downloaded {invoice.FileName} from a vendor with VAT ID {invoice.Prestatore}");
    }
}
catch (ApiException e)
{
    Console.WriteLine($"{e.Message} - {e.ErrorCode}");
}

Payload Inclusion

We set includePayload: true to retrieve the actual invoice content in the Payload property. Without this parameter, the Payload field would be null by default, which increases performance and reduces response size when you only need metadata.

Switch to the terminal(1) then type:

  1. If the terminal pane is not already open, click the Terminal menu, then New Terminal.
dotnet run

You should obtain an output similar to this one:

Received 3 invoices
Downloaded file1.xml from a vendor with VAT ID IT06157670966
Downloaded file2.xml.p7m from a vendor with VAT ID IT01280270057
Downloaded file3.xml.p7m from a vendor with VAT ID IT01280270057

The files are in the current directory, ready for you to inspect them.

Not receiving invoices in the live environment?

Ensure you registered with the Italian Revenue Service, which is a requirement for the live environment.

What we learned

In this example, we learned several things.

  1. We must configure the SDK by setting both the BasePath and Username properties, the latter initialized with the API key.

  2. We must instantiate a class representing the endpoint we want to work with. In this case, we leverage ReceiveApi to download incoming invoices.

  3. Endpoint classes like ReceiveApi offer methods for interacting with their target entity. We call ReceiveGetAsync to retrieve invoices. Because we only want new, unread invoices, we pass unread: true. We also pass includePayload: true to retrieve the actual invoice content (if you run the example a second time, you'll likely not receive any invoice unless some has arrived).

  4. The Receive class exposes valuable properties such as Encoding, FileName, and Payload. The last one contains the invoice content, as plain text or Base64-encoded, as described by Encoding.

Source Code on GitHub

The source code for this Quickstart is also available on GitHub.

Send

Create the app

The first step is to create the application:

dotnet new console -n send
The command created a new C# project named "quickstart" in a directory with the same name. Step into that directory:

cd send

Install the SDK

Once in the quickstart directory, install the C# SDK:

 dotnet add package Invoicetronic.Sdk

Once that's done, open VS Code in the current directory:

code .

Click on the Program.cs file to see its contents in VS Code right pane.

Configure the SDK

Replace Program.cs default content with the following:

Configure the SDK
using Invoicetronic.Sdk.Api;
using Invoicetronic.Sdk.Client;
using Invoicetronic.Sdk.Model;

// Configure the SDK.
var config = new Configuration
{
    BasePath = "https://api.invoicetronic.com/v1",
    Username = "YOUR TEST API KEY (starts with ik_test_)"
};

As you can see, we initialize a Configuration instance by setting the API's base path and your test API Key (not the live one). Notice how we use the Username property to set the API Key.

API Key comes in pairs

When you create your account, you obtain a pair of API Keys. One is the test key for the API Sandbox, and the other is the live API's. You can tell the difference because the former starts with ik_test_, while the latter begins with ik_live_. In this tutorial, always use the test key.

Send an invoice

We are ready to make a request. We want to send an invoice to the SDI. Add the following code:

Send and invoice
// Send an invoice
var filePath = "/some/file/path/filename.xml";

var metaData = new Dictionary<string, string>
{
    { "internal_id", "123" },
    { "created_with", "myapp" },
    { "some_other_custom_data", "value" },
};

var sendApi = new SendApi(config);

try
{
    var sentInvoice = await sendApi.SendPostAsync(new Send(payload: File.ReadAllText(filePath))
    {
        FileName = Path.GetFileName(filePath),
        MetaData = metaData
    });

    Console.WriteLine($"The invoice was sent successfully, it now has the unique Id of {sentInvoice.Id}.");
}
catch (ApiException e)
{
    Console.WriteLine($"{e.Message} - {e.ErrorCode}");
}

Switch to the terminal(1) then type:

  1. If the terminal pane is not already open, click the Terminal menu, then New Terminal.
dotnet run

You should obtain an output similar to this one:

The invoice filename.xml was sent successfully, it now has the unique Id of 123.

Check the invoice state

When you forward an invoice to the SDI, delivery is not instantaneous: the SDI runs a series of checks and returns a sequence of notifications that describe the state of the process (Inviato, Consegnato, Scartato, etc.). The Send class exposes a LatestState property with the current state, sparing you a separate /update call when you only need to know how it went.

Read the current state
// Fetch the most recent state of an already-sent invoice
var fresh = await sendApi.SendIdGetAsync(sentInvoice.Id);
Console.WriteLine($"Current state: {fresh.LatestState?.ToString() ?? "Processing"}");

Right after submission, LatestState may be null: the SDI has not processed the document yet. Check again after a few seconds or, better, configure a webhook to receive a push notification on every state change.

Save API calls

Use LatestState on Send whenever you only need the current state: a single call instead of one to /send plus one to /update. Reach for UpdateApi only when you need the full transition history.

What we learned

In this example, we learned several things.

  1. We must configure the SDK by setting both the BasePath and Username properties, the latter initialized with the API key.

  2. We must instantiate a class representing the endpoint we want to work with. In this case, we leverage SendApi to send invoices. Endpoint classes like SendApi offer methods for interacting with their target entity. We call InvoiceV1SendPosttAsync to send an invoice.

  3. The Send class exposes valuable properties such as FileName, MetaData, and Payload. The last one contains the invoice content, while MetaData is optional and binds custom data to the document.

  4. The Send class also exposes LatestState with the current SDI state, readable via SendIdGetAsync(id). It saves a /update call when you only need to know the state.

Source Code on GitHub

The source code for this Quickstart is also available on GitHub.

Update

For the current state of a sent invoice, just read LatestState from the Send class (see Check the invoice state). If instead you need the full transition history — for example to understand why an invoice was rejected, render every state transition with timestamps in your UI, or track the notifications returned by a public administration entity — use UpdateApi.

/update queries are free of charge

Requests to /update are not counted against your plan: you can poll the notification history as often as you need.

Create the app

dotnet new console -n update
cd update

Install the SDK

dotnet add package Invoicetronic.Sdk

Retrieve the notification history

Replace Program.cs content with the following:

Notification history for an invoice
using Invoicetronic.Sdk.Api;
using Invoicetronic.Sdk.Client;

// Configure the SDK
var config = new Configuration
{
    BasePath = "https://api.invoicetronic.com/v1",
    Username = "YOUR TEST API KEY (starts with ik_test_)"
};

// Id of the sent invoice we want to inspect
const int sendId = 225;

var updateApi = new UpdateApi(config);

try
{
    var updates = await updateApi.UpdateGetAsync(
        sendId: sendId,
        sort: "last_update");

    Console.WriteLine($"Found {updates.Count} notifications for invoice {sendId}");

    foreach (var update in updates)
    {
        var description = update.Description ?? "OK";
        Console.WriteLine($"  [{update.LastUpdate:O}] state={update.State} - {description}");
    }
}
catch (ApiException e)
{
    Console.WriteLine($"{e.Message} - {e.ErrorCode}");
}

Run the application:

dotnet run

You should obtain an output similar to this one:

Found 2 notifications for invoice 225
  [2025-01-23T16:56:14.1110000Z] state=Inviato - OK
  [2025-01-23T17:12:03.8420000Z] state=Consegnato - OK

The State property is the most important one. The most common values are:

Value Name Description
2 Inviato Sent to the SDI.
5 Consegnato Delivered to the recipient.
7 Scartato Rejected by the SDI. The reason is in Description.

The complete list of values is available in the API Reference. You can also filter by state, for example to retrieve only rejected invoices:

var updates = await updateApi.UpdateGetAsync(state: State.Scartato);

Always monitor the state of your sent invoices

A state of Inviato only means that the document has been accepted by the SDI, not that it has been delivered. A Scartato state indicates that the invoice was not accepted and may require a correction and a fresh submission.

What we learned

  1. To consult the notification history we use the UpdateApi class instead of SendApi or ReceiveApi.

  2. The UpdateGetAsync() method accepts filters such as sendId (notifications for a specific sent invoice), state (filter by state), lastUpdateFrom/lastUpdateTo (date range) and others.

  3. /update queries are free of charge and do not count against your plan, so you can poll them as often as you need.

Source Code on GitHub

The source code for this Quickstart is also available on GitHub.