Vai al contenuto

API fattura elettronica in C# / .NET

Invia fatture elettroniche allo SdI e ricevi fatture e notifiche dei fornitori dal tuo codice C# / .NET, con esempi completi da copiare. Ambiente: .NET 6+ e ASP.NET Core.

Prima di iniziare

  1. Crea un account e conferma l'e-mail: hai 30 giorni e 20 fatture di prova.
  2. Usa le stesse credenziali del portale per il login alle API (POST https://api.fattura-elettronica.cloud/api/login) e conserva il token.
  3. Per ricevere fatture e notifiche sul tuo server, imposta gli indirizzi dei tuoi endpoint nel profilo del portale e attiva l'inoltro.

Inviare una fattura elettronica in C# / .NET

HttpClient con MultipartFormDataContent e System.Text.Json, tutto incluso in .NET 6 e successivi. Riusa la stessa istanza di HttpClient per tutti gli invii. La risposta contiene inoltro.id_sdi, l'identificativo con cui arriveranno le notifiche della fattura.

// .NET 6+ (HttpClient e System.Text.Json già inclusi) - Program.cs
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

const string Api = "https://api.fattura-elettronica.cloud/api";
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(90) };
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

// 1. Login: il token vale a lungo, salvalo e rifai il login solo se ricevi 401
var login = await http.PostAsJsonAsync($"{Api}/login", new { email = "tua@email.it", password = "LaTuaPassword" });
var datiLogin = await login.Content.ReadFromJsonAsync<JsonElement>();
if (!datiLogin.TryGetProperty("access_token", out var token))
    throw new Exception("Login non riuscito");
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.GetString());

// 2. Invio della fattura XML allo SdI
var percorso = "IT01234567890_00001.xml";
using var form = new MultipartFormDataContent();
var file = new ByteArrayContent(await File.ReadAllBytesAsync(percorso));
file.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
form.Add(file, "fileFattura", Path.GetFileName(percorso));

var risposta = await http.PostAsync($"{Api}/inoltro", form);
var esito = await risposta.Content.ReadFromJsonAsync<JsonElement>();

if (risposta.IsSuccessStatusCode && esito.GetProperty("esito").GetString() == "ok")
    Console.WriteLine($"Inviata, identificativo SdI: {esito.GetProperty("inoltro").GetProperty("id_sdi")}");
else
    Console.WriteLine($"Errore {(int)risposta.StatusCode}: {esito.GetProperty("messaggio").GetString()}");

// 3. Elenco delle fatture inviate (esito: 0 in attesa, 1 consegnata, 2 mancata consegna, 3 scartata, ...)
var elenco = await http.GetFromJsonAsync<JsonElement>($"{Api}/inoltro");
foreach (var inoltro in elenco.GetProperty("inoltri").EnumerateArray())
    Console.WriteLine($"{inoltro.GetProperty("id_sdi")} {inoltro.GetProperty("esito")}");

Codici di risposta: 200 inviata, 400 file non valido, 401 token scaduto, 403 pacchetto esaurito o e-mail non confermata, 422 rifiutata dallo SdI. Tutti i dettagli nella documentazione completa.

Ricevere le fatture elettroniche in C# / .NET

Quando arriva una fattura per il tuo codice destinatario la mandiamo al tuo endpoint con una POST form-urlencoded: strxml (XML della fattura), xmlF (file originale), xmlM (metadati SdI), tutti in base64, e formato. Con le minimal API di ASP.NET Core leggi Request.Body come stringa, verifica la firma e poi decodifica i campi con QueryHelpers.ParseQuery.

// ASP.NET Core 6+ (minimal API) - Program.cs - URL da indicare in "Endpoint ricezione fatture"
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.WebUtilities;

var segreto = Environment.GetEnvironmentVariable("FE_SEGRETO") ?? ""; // pagina Integrazione; vuoto = senza firma
var app = WebApplication.CreateBuilder(args).Build();

app.MapPost("/ricevi-fatture", async (HttpRequest request) =>
{
    using var lettore = new StreamReader(request.Body, Encoding.UTF8);
    var corpo = await lettore.ReadToEndAsync(); // il corpo così com'è arrivato, serve per la firma
    if (!FirmaValida(request, corpo, segreto)) return Results.StatusCode(403);

    var dati = QueryHelpers.ParseQuery(corpo);
    var xmlFattura = Convert.FromBase64String(dati["strxml"].ToString()); // XML, già estratto dal .p7m
    var metadati = Encoding.UTF8.GetString(Convert.FromBase64String(dati["xmlM"].ToString()));
    var idSdi = Regex.Match(metadati, @"<IdentificativoSdI>(\d+)<").Groups[1].Value;
    if (idSdi == "" || xmlFattura.Length == 0) return Results.BadRequest("dati mancanti");

    // Stesso IdentificativoSdI = stessa fattura: se arriva di nuovo sovrascrivi, non duplicare
    Directory.CreateDirectory("fatture_ricevute");
    await File.WriteAllBytesAsync(Path.Combine("fatture_ricevute", $"{idSdi}.xml"), xmlFattura);

    return Results.Text("OK"); // esattamente OK: con qualsiasi altra risposta viene rimandata
});

app.Run();

// X-FE-Firma = HMAC-SHA256 di "timestamp\ncorpo" con il tuo segreto
static bool FirmaValida(HttpRequest req, string corpo, string segreto)
{
    if (segreto == "") return true;
    var ts = req.Headers["X-FE-Timestamp"].ToString();
    if (!long.TryParse(ts, out var t) || Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > 300) return false;
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(segreto));
    var attesa = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(ts + "\n" + corpo))).ToLowerInvariant();
    return CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(attesa),
        Encoding.ASCII.GetBytes(req.Headers["X-FE-Firma"].ToString()));
}

Rispondi OK con HTTP 200 solo dopo aver salvato la fattura. Se il tuo server non risponde riproviamo per circa 4 giorni: la stessa fattura può arrivare più volte, riconoscila da IdentificativoSdI.

Ricevere le notifiche SdI in C# / .NET

Ogni notifica dello SdI (consegna, scarto, mancata consegna, esiti della PA) arriva al tuo endpoint notifiche con i campi tipoNotifica e esitojson. Il significato di ogni notifica è spiegato nella guida alle notifiche SdI.

// ASP.NET Core - nello stesso Program.cs dell'endpoint fatture (FirmaValida è quella)
// URL da indicare in "Endpoint ricezione notifiche"
var tipi = new Dictionary<string, string> { ["1"] = "RC", ["2"] = "MC", ["3"] = "NS", ["4"] = "NE", ["5"] = "DT", ["6"] = "AT" };
var stati = new Dictionary<string, string>
{
    ["RC"] = "consegnata",
    ["MC"] = "mancata consegna",     // disponibile nel cassetto fiscale del cliente
    ["NS"] = "scartata",             // i motivi sono in ListaErrori
    ["NE"] = "esito committente",    // solo PA: EsitoCommittente.Esito = EC01 accettata, EC02 rifiutata
    ["DT"] = "decorrenza termini",   // solo PA: nessuna risposta entro 15 giorni
    ["AT"] = "attestazione trasmissione",
};

app.MapPost("/ricevi-notifiche", async (HttpRequest request) =>
{
    using var lettore = new StreamReader(request.Body, Encoding.UTF8);
    var corpo = await lettore.ReadToEndAsync();
    if (!FirmaValida(request, corpo, segreto)) return Results.StatusCode(403);

    var dati = QueryHelpers.ParseQuery(corpo);
    if (!tipi.TryGetValue(dati["tipoNotifica"].ToString(), out var tipo)) return Results.BadRequest("dati mancanti");
    using var notifica = JsonDocument.Parse(dati["esitojson"].ToString()); // la notifica SdI in JSON
    var idSdi = notifica.RootElement.GetProperty("IdentificativoSdI").ToString();

    // IdentificativoSdI è lo stesso restituito dall'API all'invio (inoltro.id_sdi).
    // La stessa notifica può arrivare due volte: l'aggiornamento deve poter essere ripetuto.
    await AggiornaFattura(idSdi, stati[tipo], dati["esitojson"].ToString());

    return Results.Text("OK"); // esattamente OK: con qualsiasi altra risposta viene rimandata
});

Prossimi passi

Inizia con C# / .NET

Prova gratis 30 giorni con 20 fatture, poi da 5 € al mese.