C# .NET
Complete HTTP client for integrating from a .NET application (console, ASP.NET, worker). Includes issuing, querying, downloading PDF/XML, resending email, voiding, next consecutive number, and a webhook receiver with HMAC verification.
Setup
dotnet new console -n LxsIntegration
cd LxsIntegration
dotnet add package System.Text.JsonComplete client
The LxsClient wraps HttpClient with a 30 s timeout and the authentication headers (X-Api-Key). Each call to EmitirAsync generates a unique Idempotency-Key if one is not provided.
// 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.pos.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, int IdUsuario, 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);Webhook receiver (ASP.NET)
The controller reads the raw body, computes the HMAC-SHA256 with LXS_WEBHOOK_SECRET, and compares it against the X-Lxs-Signature header using FixedTimeEquals to prevent timing attacks. If the signature does not match, it responds 401; if it matches, it deserializes the event and processes it.
[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);
// Process event
return Ok();
}
}
public record LxsEvent(string Evento, string Clave, string IndEstado, string Mensaje, int Intento, string Nonce);