Use a computer

For full performance and fluidity, please open Pay Engineers on a desktop or laptop. On mobile, the experience is limited — especially authenticated sections and advanced tools after login.

dotnet

.NET HttpClient sample

A .NET example showing how to consume sandbox and production endpoints from ASP.NET or a console application, including dependency injection, typed clients, Polly retry policies and secret configuration.

Why HttpClient via DI

In ASP.NET, prefer registering a named or typed HttpClient through IHttpClientFactory rather than instantiating new HttpClient() directly — the factory manages the underlying connection pool correctly and avoids the socket exhaustion issues that come from creating and disposing clients per request.

Configuration

Store the base URL and key in appsettings.json for structure, but resolve the actual key value from an environment variable, user secrets (development) or a managed secret store (production) via configuration providers — never commit real key values:

{
  "Processor": {
    "BaseUrl": "https://sandbox.example.com"
  }
}
# Environment variable, resolved by the configuration system as Processor__ApiKey
Processor__ApiKey=sk_sandbox_xxxxxxxxxxxxxxxx

Registering a typed client

builder.Services.AddHttpClient<ProcessorClient>(client =>
{
    client.BaseAddress = new Uri(configuration["Processor:BaseUrl"]!);
    client.Timeout = TimeSpan.FromSeconds(10);
})
.AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(
    3, attempt => TimeSpan.FromMilliseconds(200 * Math.Pow(2, attempt))
));

.NET example

public class ProcessorClient
{
    private readonly HttpClient _client;
    private readonly string _apiKey;

    public ProcessorClient(HttpClient client, IConfiguration configuration)
    {
        _client = client;
        _apiKey = configuration["Processor:ApiKey"]
            ?? throw new InvalidOperationException("Processor:ApiKey is not configured.");
    }

    public async Task<PingResult> PingAsync(CancellationToken ct = default)
    {
        using var request = new HttpRequestMessage(HttpMethod.Get, "/v1/ping");
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        using var response = await _client.SendAsync(request, ct);

        if (!response.IsSuccessStatusCode)
        {
            var body = await response.Content.ReadAsStringAsync(ct);
            throw new ProcessorException($"Processor returned {(int)response.StatusCode}: {body}");
        }

        return await response.Content.ReadFromJsonAsync<PingResult>(cancellationToken: ct)
            ?? throw new ProcessorException("Processor returned an empty response body.");
    }
}

Retries and resilience

The AddTransientHttpErrorPolicy call above uses Polly to retry transient failures (5xx and connection errors) with exponential backoff, matching the same pattern recommended for the other SDKs in this section. Combine it with a circuit breaker policy if your service calls the processor at high volume, so a prolonged outage fails fast instead of queuing retries indefinitely.

Error handling

Distinguish HttpRequestException (network/TLS/DNS failures), a non-success status code (surfaced here as ProcessorException), and TaskCanceledException thrown when the configured Timeout elapses. Logging the processor's JSON error code from the response body, not just the HTTP status, makes triage significantly faster.

Security notes

  • Use dotnet user-secrets in local development instead of putting real keys in appsettings.Development.json.
  • In production, resolve Processor:ApiKey from Azure Key Vault, AWS Secrets Manager, or environment variables injected by your orchestrator — never bake secrets into container images.
  • Keep HttpClient.Timeout set explicitly; the default of 100 seconds is too long for a synchronous compliance/ping check and can hold connections open longer than necessary under load.