Pular para o conteúdo
xFlow Payments

Blog

API Pix em C#

Para integrar a API Pix em C#, registre um HttpClient tipado com Basic auth (pk_live_ como usuário, sk_live_ como senha), envie POST /api/v1/charges, mostre o pix.qrCodeBase64 e o pix.copyPaste da resposta e confirme o pagamento pelo webhook transaction.paid, validando o HMACSHA256 do corpo cru com CryptographicOperations.FixedTimeEquals. Antes da produção, teste com chaves pk_test_.

Atualizado em · Equipe xFlow Payments · 7 min de leitura

Em C#, a API Pix se integra com o HttpClient do próprio .NET: um POST cria a cobrança, a resposta já traz o QR Code e o copia e cola, e um endpoint do ASP.NET Core recebe o webhook que confirma o pagamento. O código abaixo segue a documentação da API, usa .NET 8 ou superior e a base https://app.xflowpayments.com/api/v1. Para a visão geral da integração, veja o guia da API Pix.

O que você precisa

  • Uma chave gerada no painel, em Desenvolvedores > Chaves de API. A chave pública (pk_live_...) fica sempre visível. O segredo (sk_live_...) aparece uma única vez.
  • Para testar, uma chave do ambiente Teste (pk_test_... e sk_test_...), que pode ser criada antes da aprovação do cadastro.
  • .NET 8 ou superior, sem pacote extra.
  • As chaves na configuração: Xflow:PublicKey, Xflow:SecretKey e Xflow:WebhookSecret. Em desenvolvimento, use dotnet user-secrets. Em produção, variáveis de ambiente com dois sublinhados, como Xflow__SecretKey.

O segredo fica no servidor. Nunca o coloque em código versionado, em Blazor WebAssembly ou num app que roda no aparelho do cliente.

Um HttpClient tipado

A autenticação é HTTP Basic: usuário é a chave pública, senha é o segredo. Registre o cliente uma vez no Program.cs. O AddHttpClient gerencia as conexões e evita o problema clássico de criar um HttpClient novo a cada chamada.

csharp
// Program.cs
using System.Net.Http.Headers;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient<XflowClient>(http =>
{
    http.BaseAddress = new Uri("https://app.xflowpayments.com/api/v1/"); // termina em "/"
    var cred = $"{builder.Configuration["Xflow:PublicKey"]}:{builder.Configuration["Xflow:SecretKey"]}";
    http.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes(cred)));
    http.Timeout = TimeSpan.FromSeconds(30);
});
csharp
// XflowClient.cs
using System.Net.Http.Json;
using System.Text.Json.Nodes;

public class XflowClient(HttpClient http)
{
    public async Task<JsonNode?> ChamarAsync(HttpMethod metodo, string caminho,
        object? corpo = null, string? idempotencyKey = null, CancellationToken ct = default)
    {
        using var req = new HttpRequestMessage(metodo, caminho); // sem "/" no início
        if (corpo is not null) req.Content = JsonContent.Create(corpo);
        if (idempotencyKey is not null) req.Headers.Add("Idempotency-Key", idempotencyKey);

        using var resp = await http.SendAsync(req, ct);
        var texto = await resp.Content.ReadAsStringAsync(ct);
        var json = texto.Length > 0 ? JsonNode.Parse(texto) : null;
        if (!resp.IsSuccessStatusCode)
            throw new XflowException((int)resp.StatusCode, json, resp.Headers.RetryAfter?.Delta);
        return json;
    }
}

public class XflowException(int status, JsonNode? corpo, TimeSpan? retryAfter)
    : Exception($"{status} {corpo?["error"]}: {corpo?["message"]} ({corpo?["request_id"]})")
{
    public int Status { get; } = status;
    public string? Codigo { get; } = (string?)corpo?["error"];
    public TimeSpan? RetryAfter { get; } = retryAfter;
}

Atenção à barra: com BaseAddress terminando em /api/v1/, o caminho relativo é charges. Escrever /charges aponta para a raiz do domínio. Toda resposta traz um request_id (req_...), no corpo e no header x-request-id, e a mensagem da exceção já o inclui. Com ele você encontra a chamada em Integrações > Logs da API, que guarda cada requisição autenticada por 30 dias.

Criar a cobrança Pix

Injete o XflowClient no seu serviço ou endpoint e chame:

csharp
var pedidoId = "1234";
var cobranca = await xflow.ChamarAsync(HttpMethod.Post, "charges", new
{
    amountCents = 1990, // R$ 19,90
    customer = new { name = "Cliente Exemplo", email = "cliente@exemplo.com", document = "00000000000" },
    description = $"Pedido #{pedidoId}",
    external_reference = $"pedido-{pedidoId}",
}, idempotencyKey: $"pedido-{pedidoId}");

var id = (string?)cobranca?["id"]; // "clx...", status "pending"
var qrCode = (string?)cobranca?["pix"]?["qrCodeBase64"];
var copiaECola = (string?)cobranca?["pix"]?["copyPaste"];

Três detalhes:

  1. Nomes do JSON. O JsonContent usa a política camelCase. No tipo anônimo, os nomes já saem certos. Se trocar por um record com ExternalReference, ele vira externalReference: marque a propriedade com [JsonPropertyName("external_reference")].
  2. Idempotency-Key única por pedido. Repetir a chamada com a mesma chave devolve a mesma cobrança, sem gerar outro Pix. Com outro corpo, a resposta é 409 idempotency_conflict. Veja idempotência em API de pagamento.
  3. Centavos. Se o total está em decimal de reais, multiplique por 100 e confira que não sobrou fração antes de converter para int. Valor fora do mínimo ou do máximo da conta retorna 400 validation.

Mostrar o QR Code e o copia e cola

pix.qrCodeBase64 é um PNG em data-URL, pronto para o src. Numa view Razor:

cshtml
<img src="@Model.QrCode" alt="QR Code Pix" width="240" height="240" />
<textarea readonly>@Model.CopiaECola</textarea>

Num app MAUI ou desktop, pegue os bytes do PNG com Convert.FromBase64String(qrCode[(qrCode.IndexOf(',') + 1)..]). pix.copyPaste é o código BR Code que o pagador cola no app do banco: repasse sem alterar. Os dois campos vêm na criação e em GET /api/v1/charges/{id}, mas não na listagem. Mais em QR Code Pix dinâmico por API.

Receber o webhook com Minimal API

Cadastre a URL em Integrações > Webhooks. Ela precisa ser https:// e pública. Cada entrega é um POST JSON com os headers x-webhook-event e x-webhook-signature: sha256=..., o HMAC-SHA256 do corpo cru com o segredo do endpoint.

csharp
// Program.cs, depois de var app = builder.Build();
// no topo do arquivo: using System.Security.Cryptography; using System.Text.Json.Nodes;
var segredo = Encoding.UTF8.GetBytes(app.Configuration["Xflow:WebhookSecret"]!);

app.MapPost("/webhooks/xflow", async (HttpRequest request, PedidoService pedidos) =>
{
    using var ms = new MemoryStream();
    await request.Body.CopyToAsync(ms); // corpo cru, antes de qualquer desserialização
    var corpo = ms.ToArray();

    var esperado = "sha256=" + Convert.ToHexString(HMACSHA256.HashData(segredo, corpo)).ToLowerInvariant();
    var recebido = request.Headers["x-webhook-signature"].ToString();
    if (!CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(esperado), Encoding.UTF8.GetBytes(recebido)))
        return Results.Unauthorized();

    var evento = JsonNode.Parse(corpo)!;
    var data = evento["data"]!;
    if ((string?)evento["event"] == "transaction.paid" && (bool?)data["livemode"] == true)
        await pedidos.DarBaixaAsync((string?)data["external_reference"], (string?)data["pix"]?["endToEnd"]);

    return Results.Ok(); // exceção acima vira 500 e a entrega é reenviada
});

PedidoService é a sua camada de negócio, registrada no contêiner de serviços. Os cuidados:

  • Não declare o corpo como parâmetro tipado. Um record no lugar do HttpRequest faria o ASP.NET Core desserializar o JSON, e você perderia os bytes originais que o HMAC exige.
  • Convert.ToHexString devolve maiúsculas. A assinatura vem em minúsculas, por isso o ToLowerInvariant().
  • FixedTimeEquals compara em tempo constante. Não use == para assinatura.
  • Responda 2xx. Sem isso, a xFlow reenvia até 3 vezes. A baixa precisa ignorar pedido que já está pago.
  • Filtre livemode: false, que marca eventos gerados com chave de teste.

Se a aplicação exige autenticação em todas as rotas, marque esta com .AllowAnonymous(): quem protege o endpoint é a assinatura. Todos os eventos, incluindo estorno e saque, estão em webhook Pix.

Testar no sandbox

Com a chave de teste na configuração, o código é o mesmo. As respostas trazem livemode: false, nenhum provedor Pix é chamado e o pix.copyPaste é um marcador que não pode ser pago.

csharp
var teste = await xflow.ChamarAsync(HttpMethod.Post, "charges", new
{
    amountCents = 1990,
    customer = new { name = "Cliente", email = "c@exemplo.com", document = "00000000000" },
});
var testeId = (string?)teste?["id"];

await xflow.ChamarAsync(HttpMethod.Post, $"test/charges/{testeId}/pay");

var paga = await xflow.ChamarAsync(HttpMethod.Get, $"charges/{testeId}");
Console.WriteLine($"{paga?["status"]} {paga?["livemode"]}"); // paid false

A cobrança vira paid, a taxa sai das taxas reais da sua conta e o webhook transaction.paid chega com livemode: false. Com chave de produção, a simulação responde 403 test_mode_only. Saques não rodam em teste (403 test_mode_unsupported). O passo a passo está em sandbox da API Pix.

Erros e limite de requisições

HTTP Código O que fazer
401 unauthorized Confira as chaves e, se usa allowlist de IP, o IP do servidor.
400 validation Corrija o corpo. O campo details aponta o problema.
403 account_inactive Conta em análise ou bloqueada. Siga com a chave de teste.
429 rate_limited Espere o retry-after e repita com backoff.
502 charge_failed Falha ao gerar a cobrança no provedor. Repita com backoff.

O limite é por conta, e toda resposta autenticada traz x-ratelimit-limit, x-ratelimit-remaining e x-ratelimit-reset. Para o 429, um filtro de exceção resolve:

csharp
async Task<JsonNode?> ComRetentativa(Func<Task<JsonNode?>> chamada)
{
    for (var tentativa = 1; ; tentativa++)
    {
        try { return await chamada(); }
        catch (XflowException e) when (e.Status == 429 && tentativa < 3)
        {
            await Task.Delay(e.RetryAfter ?? TimeSpan.FromSeconds(1 << tentativa));
        }
    }
}

Numa falha de rede ao criar a cobrança, repita com a mesma Idempotency-Key. A lista de códigos está em erros da API Pix.

Antes de ir para produção

  1. Troque pk_test_/sk_test_ por pk_live_/sk_live_ só na configuração de produção.
  2. Cadastre a URL de produção do webhook e guarde o segredo do endpoint.
  3. Mantenha o filtro de livemode e a baixa idempotente.
  4. Registre o request_id de toda resposta com erro.
  5. Se quiser, ative a allowlist de IP em Chaves de API. Fora da lista, a resposta é 401.

Prefira o webhook ao polling de GET /api/v1/charges/{id}. O mesmo fluxo em outras linguagens está em API Pix em Java e API Pix em Node.js, e a página Desenvolvedores reúne chaves, logs e todos os guias.

Perguntas frequentes

Preciso de pacote NuGet para usar a API Pix em C#?

Não. HttpClient, System.Net.Http.Json e System.Text.Json já vêm no .NET. A API da xFlow é REST com JSON e autenticação HTTP Basic, que você monta no header Authorization com a chave pública e o segredo.

Por que a chamada vai para o endereço errado quando uso BaseAddress?

Porque o caminho relativo começa com barra. Com BaseAddress terminando em /api/v1/, use "charges", sem barra no início. Com "/charges", o HttpClient descarta o /api/v1 e chama a raiz do domínio.

Como validar o webhook Pix no ASP.NET Core?

Leia o corpo cru de request.Body para um array de bytes, calcule HMACSHA256.HashData com o segredo do endpoint, converta para hexadecimal em minúsculas, prefixe sha256= e compare com o header x-webhook-signature usando CryptographicOperations.FixedTimeEquals. Só depois desserialize o JSON.

Como testar a API Pix em C# sem pagar de verdade?

Use uma chave de teste (pk_test_/sk_test_), crie a cobrança normalmente e chame POST /api/v1/test/charges/{id}/pay. A cobrança vira paid e o webhook transaction.paid chega com livemode: false.

Continue lendo

SoluçãoAPI PixA API Pix da xFlow Payments é uma API REST para criar cobranças Pix com QR Code e copia e cola, estornar, sacar e receber webhooks assinados. Uma única integração conecta a sua operação a 11 adquirentes, com roteamento por regras e retentativa automática.BlogWebhook Pix: eventos, assinatura HMAC e boas práticasWebhook Pix é o aviso que a API envia para a sua URL quando algo acontece com uma cobrança ou um saque, como o pagamento confirmado. Na xFlow, cada aviso é um POST JSON assinado com HMAC-SHA256, reenviado até 3 vezes até você responder 2xx.BlogAPI Pix sandbox: como testar sem mover dinheiroO sandbox da API Pix da xFlow funciona com chaves pk_test_ e sk_test_: mesmos endpoints, mesmas respostas com livemode: false, nenhum dinheiro real e um endpoint para simular o pagamento que dispara o webhook transaction.paid.BlogErros da API PixNa API Pix da xFlow, todo erro volta em JSON com error (o código), message e request_id. O status HTTP diz a família do problema: 400 corpo inválido, 401 credencial, 403 conta ou modo, 404 recurso inexistente, 409 idempotência, 422 recusa de negócio, 429 limite e 502 falha no provedor. O request_id localiza a chamada em Integrações > Logs da API.BlogAPI Pix em JavaPara integrar a API Pix em Java, envie POST /api/v1/charges com o java.net.http.HttpClient e Basic auth (pk_live_ como usuário, sk_live_ como senha), mostre o pix.qrCodeBase64 e o pix.copyPaste da resposta e confirme o pagamento pelo webhook transaction.paid, validando o HMAC-SHA256 do corpo cru com javax.crypto.Mac e MessageDigest.isEqual. Antes da produção, teste com chaves pk_test_.