C# .NET
Cliente HTTP completo para integrarte desde una aplicación .NET (consola, ASP.NET, worker). Incluye emisión, consulta, descarga de PDF/XML, reenvío de correo, anulación, próximo consecutivo y un receptor de webhooks con verificación HMAC.
Setup
dotnet new console -n LxsIntegration
cd LxsIntegration
dotnet add package System.Text.JsonCliente completo
El LxsClient envuelve HttpClient con timeout de 30 s y los headers de autenticación (X-Api-Key). Cada llamada a EmitirAsync genera un Idempotency-Key único si no se proporciona uno.
// LxsClient.cs
using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Http.Headers;
public class LxsClient
{
private readonly HttpClient _http;
public LxsClient(string apiKey, string baseUrl = "https://api.lxsyscr.com")
{
_http = new HttpClient
{
BaseAddress = new Uri(baseUrl),
Timeout = TimeSpan.FromSeconds(30)
};
_http.DefaultRequestHeaders.Add("X-Api-Key", apiKey);
_http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<FacturaResponse> EmitirAsync(FacturaRequest data, string? idempotencyKey = null)
{
var req = new HttpRequestMessage(HttpMethod.Post, "/api/v1/facturas")
{
Content = JsonContent.Create(data)
};
req.Headers.Add("Idempotency-Key", idempotencyKey ?? Guid.NewGuid().ToString());
var res = await _http.SendAsync(req);
res.EnsureSuccessStatusCode();
return (await res.Content.ReadFromJsonAsync<FacturaResponse>())!;
}
public async Task<FacturaResponse> ConsultarAsync(string clave)
=> (await _http.GetFromJsonAsync<FacturaResponse>($"/api/v1/facturas/{clave}"))!;
public async Task<byte[]> DescargarPdfAsync(string clave)
=> await _http.GetByteArrayAsync($"/api/v1/facturas/{clave}/pdf");
public async Task<string> DescargarXmlAsync(string clave)
=> await _http.GetStringAsync($"/api/v1/facturas/{clave}/xml");
public async Task ReenviarEmailAsync(string clave)
{
var res = await _http.PostAsync($"/api/v1/facturas/{clave}/email", null);
res.EnsureSuccessStatusCode();
}
public async Task AnularAsync(string clave)
{
var res = await _http.DeleteAsync($"/api/v1/facturas/{clave}");
res.EnsureSuccessStatusCode();
}
public async Task<long> ProximoConsecutivoAsync(string tipo)
{
var res = await _http.GetFromJsonAsync<ConsecutivoResponse>($"/api/v1/consecutivos/proximo?tipo={tipo}");
return res!.Proximo;
}
}
public record FacturaRequest(
string Tipo, ClienteDto? Cliente, string CondicionVenta, string MedioPago,
string Moneda, decimal? TipoCambio, List<LineaDto> Lineas, List<ReferenciaDto> Referencias);
public record ClienteDto(int IdCliente);
public record LineaDto(int IdProducto, decimal Cantidad, decimal? PrecioUnitario = null, decimal MontoDescuento = 0);
public record ReferenciaDto(string TipoDocumento, string Numero, DateTime? FechaEmision, string? Codigo, string? Razon);
public record FacturaResponse(
string Clave, string NumeroConsecutivo, string TipoDocumento,
string EstadoHacienda, decimal TotalComprobante, string Moneda, DateTime FechaEmision);
public record ConsecutivoResponse(string Tipo, long Proximo);Receptor de webhook (ASP.NET)
El controller lee el body crudo, calcula el HMAC-SHA256 con el LXS_WEBHOOK_SECRET y lo compara contra el header X-Lxs-Signature con FixedTimeEquals para evitar ataques de timing. Si la firma no coincide responde 401; si coincide, deserializa el evento y lo procesa.
[ApiController]
[Route("webhooks/lxs")]
public class LxsWebhookController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Handle()
{
using var reader = new StreamReader(Request.Body);
string body = await reader.ReadToEndAsync();
string presented = Request.Headers["X-Lxs-Signature"]
.ToString()
.Replace("sha256=", "", StringComparison.OrdinalIgnoreCase);
var secret = Environment.GetEnvironmentVariable("LXS_WEBHOOK_SECRET")!;
var expectedBytes = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes(body));
string expected = Convert.ToHexString(expectedBytes).ToLowerInvariant();
if (presented.Length != expected.Length ||
!CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(presented),
Encoding.UTF8.GetBytes(expected)))
{
return Unauthorized();
}
var evento = JsonSerializer.Deserialize<LxsEvent>(body);
// Procesa evento
return Ok();
}
}
public record LxsEvent(string Evento, string Clave, string IndEstado, string Mensaje, int Intento, string Nonce);