.NET · HttpClient

Integración de VeriFactu con .NET

Si estás desarrollando un ERP, un SaaS de facturación o cualquier software que necesite cumplir con la normativa VeriFactu, esta guía te muestra el camino más corto desde cero hasta emitir tu primera factura verificada con .NET.

VeriFactuAPI es un servicio SaaS que abstrae toda la complejidad del protocolo de la AEAT: la generación del XML firmado, la cadena de huellas, el envío y la gestión de respuestas. Tú llamas a una API REST y nosotros nos encargamos del resto. No necesitas certificados propios, ni lidiar con los XSD de Hacienda, ni mantener la infraestructura de envío.

Esta guía usa HttpClient y System.Text.Json, ambos incluidos en el framework desde .NET 5. No necesitas SDK oficial ni dependencias externas: la API es REST pura y puedes integrarla con las clases estándar de .NET.

Requisitos e instalación

No necesitas instalar librerías adicionales para esta integración. Solo necesitas:

  • .NET 6 o superior
  • Una cuenta en verifactuapi.es (el plan gratuito incluye entorno de pruebas)
  • Clases nativas: HttpClient, System.Text.Json y la cabecera Authorization: Bearer <token>

No existe un SDK oficial específico para .NET: la integración se hace directamente contra la API REST, lo cual significa cero dependencias propietarias y total control sobre tu código.

Flujo completo: de la autenticación a la factura registrada

El flujo para enviar una factura tiene tres pasos: autenticarte, generar el registro de facturación y comprobar su estado en la AEAT. Esta comprobación puede hacerse mediante webhook (recomendado) o con consulta manual (polling).

Paso 1 — Autenticación y obtención del token

C#
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Nodes;

private static readonly HttpClient _httpClient = new HttpClient
{
    BaseAddress = new Uri("https://app.verifactuapi.es/api/"),
    Timeout = TimeSpan.FromSeconds(30),
};

public static async Task<string> LoginAsync(string email, string password)
{
    var payload = new { email, password };

    HttpResponseMessage response;
    try
    {
        response = await _httpClient.PostAsJsonAsync("login", payload);
    }
    catch (HttpRequestException ex)
    {
        throw new InvalidOperationException($"Error de red en login: {ex.Message}", ex);
    }

    var rawResponse = await response.Content.ReadAsStringAsync();

    JsonNode? data;
    try
    {
        data = JsonNode.Parse(rawResponse);
    }
    catch (JsonException ex)
    {
        throw new InvalidOperationException("Respuesta JSON inválida en login.", ex);
    }

    if (data is null)
        throw new InvalidOperationException("Respuesta JSON inválida en login.");

    if (data["success"]?.GetValue<bool>() != true)
    {
        var code    = data["code"]?.ToString();
        var message = data["message"]?.ToString();
        throw new InvalidOperationException($"Login fallido: {code}, {message}");
    }

    return data["token"]!.GetValue<string>();
}

Ejemplo de uso

C#
string token = await LoginAsync("tu@email.com", "tu_password");
El token tiene una validez de 4 horas. Puedes generar uno nuevo en cada ciclo de ejecución o guardarlo en caché (IMemoryCache, Redis o una variable estática) y renovarlo al caducar para reducir llamadas de login.

Paso 2 — Generar registro de facturación

Este es el núcleo de la integración. El siguiente ejemplo cubre el caso más habitual: una factura ordinaria (F1) con IVA al 21%.

C#
public static async Task<JsonNode> RegistrarFacturaAsync(string token, object factura)
{
    using var request = new HttpRequestMessage(HttpMethod.Post, "alta-registro-facturacion");
    request.Headers.Authorization =
        new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
    request.Content = JsonContent.Create(factura);

    HttpResponseMessage response;
    try
    {
        response = await _httpClient.SendAsync(request);
    }
    catch (HttpRequestException ex)
    {
        throw new InvalidOperationException($"Error de red al registrar factura: {ex.Message}", ex);
    }

    var rawResponse = await response.Content.ReadAsStringAsync();

    JsonNode? data;
    try
    {
        data = JsonNode.Parse(rawResponse);
    }
    catch (JsonException ex)
    {
        throw new InvalidOperationException("Respuesta JSON inválida al registrar factura.", ex);
    }

    if (data is null)
        throw new InvalidOperationException("Respuesta JSON inválida al registrar factura.");

    if (data["success"]?.GetValue<bool>() != true)
        throw new InvalidOperationException($"Error al registrar factura: {data["message"]}");

    return data["data"]!["items"]![0]!;
}

Ejemplo de uso

C#
var datosFactura = new
{
    IDEmisorFactura        = "A39200019",
    NumSerieFactura        = "FAC/2025/0001",
    FechaExpedicionFactura = "2025-04-22",
    TipoFactura            = "F1",
    DescripcionOperacion   = "Servicios de desarrollo web - Abril 2025",
    EmitidaPorTercODesti   = (string?)null,

    Destinatarios = new[]
    {
        new
        {
            NombreRazon = "Cliente Ejemplo S.A.",
            NIF         = "39707287H",
        },
    },

    Desglose = new[]
    {
        new
        {
            Impuesto                      = 1,
            ClaveRegimen                  = 1,
            CalificacionOperacion         = 1,
            TipoImpositivo                = 21,
            BaseImponibleOImporteNoSujeto = 1000,
            CuotaRepercutida              = 210,
        },
    },

    CuotaTotal   = 210,
    ImporteTotal = 1210,
};

var factura    = await RegistrarFacturaAsync(token, datosFactura);
var registroId = factura["id"]!.GetValue<int>();
var qrImageB64 = factura["qr_image"]!.GetValue<string>();
var urlQr      = factura["url_qr"]?.GetValue<string>();
var estadoAeat = factura["estado_aeat"]!.GetValue<string>();

Console.WriteLine($"Factura registrada con ID: {registroId}");
Console.WriteLine($"Estado inicial: {estadoAeat}");
Console.WriteLine($"URL validación AEAT: {urlQr ?? "No disponible"}");

Paso 3 — Consultar el estado en la AEAT

El envío a la AEAT es asíncrono. La factura se registra inmediatamente en nuestra plataforma con estado "No Registrado" y se envía en el siguiente ciclo de proceso. Puedes consultar el estado de dos formas:

Opción A — Polling manual

C#
public static async Task<(string EstadoAeat, string? CodigoError, string? DescripcionError)>
    ConsultarEstadoAsync(string token, int registroId)
{
    using var request = new HttpRequestMessage(
        HttpMethod.Get,
        $"alta-registro-facturacion/{registroId}"
    );
    request.Headers.Authorization =
        new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

    HttpResponseMessage response;
    try
    {
        response = await _httpClient.SendAsync(request);
    }
    catch (HttpRequestException ex)
    {
        throw new InvalidOperationException($"Error de red al consultar estado: {ex.Message}", ex);
    }

    var rawResponse = await response.Content.ReadAsStringAsync();

    JsonNode? data;
    try
    {
        data = JsonNode.Parse(rawResponse);
    }
    catch (JsonException ex)
    {
        throw new InvalidOperationException("Respuesta JSON inválida al consultar estado.", ex);
    }

    if (data is null)
        throw new InvalidOperationException("Respuesta JSON inválida al consultar estado.");

    if (data["success"]?.GetValue<bool>() != true)
        throw new InvalidOperationException(
            $"Error al consultar el estado de la factura: {data["message"]}"
        );

    var items = data["data"]!["items"]!;
    return (
        EstadoAeat:       items["estado_aeat"]!.GetValue<string>(),
        CodigoError:      items["codigo_error_aeat"]?.GetValue<string>(),
        DescripcionError: items["descripcion_error_aeat"]?.GetValue<string>()
    );
}

Ejemplo de uso

C#
var (estadoAeat, codigoError, descripcionError) =
    await ConsultarEstadoAsync(token, registroId);

Console.WriteLine($"Estado AEAT: {estadoAeat}");
// Valores posibles: "No Registrado" | "Pendiente" | "Incorrecto" | "Correcto"

Opción B — Webhook (recomendado)

Configura un endpoint en tu aplicación y registra la URL en el panel de VeriFactuAPI. Recibirás una notificación en cuanto la AEAT responda, sin necesidad de hacer polling:

JSON
{
  "estado": "Correcto",
  "num_serie": "FAC/2025/0001",
  "factura_id": 1,
  "codigo_error": "",
  "descripcion_error": "",
  "fecha_notificacion": "YYYY-MM-DD HH:MM:SS"
}

Formatos de respuesta JSON

Las respuestas de la API siguen una estructura JSON consistente. Entender estos formatos te permite validar respuestas de forma uniforme con System.Text.Json.

Formato 1 — success (operación correcta)

JSON
{
  "success": true,
  "message": "Inicio de sesión exitoso",
  "code": 200,
  "token": "eyJ...",
  "expires_at": "2026-04-24 18:30:00"
}
  • success: true cuando la operación finaliza correctamente.
  • message: texto descriptivo del resultado.
  • code: código HTTP devuelto por la API.

Campos opcionales: token, expires_at, user_name, user_email, api_key, etc.

Formato 2 — fail (error)

JSON
{
  "success": false,
  "message": "El campo IDEmisorFactura es obligatorio.",
  "error": "VALIDATION_ERROR",
  "code": 400
}
  • success: false cuando la operación falla.
  • message: descripción del error para mostrar o registrar en logs.
  • error: código interno o técnico del error.
  • code: código HTTP asociado al fallo.

Formato 3 — list (listados)

JSON
{
  "success": true,
  "message": "Invoice listed successfully",
  "code": 200,
  "data": {
    "items": [],
    "count": 0
  },
  "pagination": {
    "total": 0,
    "perPage": 0,
    "currentPage": 0,
    "lastPage": 0
  }
}
  • data.items: array con resultados.
  • data.count: número de elementos devueltos en la página actual.
  • pagination: bloque con metadatos de paginación.

Próximos pasos

  • Consulta la referencia completa de la API para ver todos los parámetros disponibles (facturas rectificativas, TicketBAI, webhooks, etc.)
  • Revisa las listas de valores (/api/listas) para conocer todos los códigos válidos de TipoFactura, Impuesto, ClaveRegimen y CalificacionOperacion

¿Listo para probarlo?

Crea tu cuenta gratuita y emite tu primera factura verificada en menos de 10 minutos. El entorno de pruebas está completamente habilitado desde el primer día: sin tarjeta, sin límites artificiales, con la misma API que usarás en producción.