.NET
The .NET section is the most complete. The same concepts — singleton configuration, OAuth 2.0, retry, error handling, logging — apply to every other language. Skip ahead if you're using a different stack.
Step 0 — Configure the nuget feed
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="WexHealth-Public" value="https://pkgs.dev.azure.com/wexhealth/_packaging/SDK/nuget/v3/index.json" />
</packageSources>
</configuration> Add the following in your project file and replace the version you want to pin:
<ItemGroup>
<PackageReference Include="WexHealth.SDK" Version="1.2.3" />
</ItemGroup> Step 1 — Install
dotnet add package WexHealthStep 2 — Configure
The SDK's AddWexHealthSdk extension method wires IHttpClientFactory, connection pooling, logger injection, and transient DI lifetimes — all in a single call in Program.cs. The underlying IHttpClientFactory is a singleton: one registered, shared connection pool for the lifetime of the application. Creating HttpClient instances directly per request defeats connection pooling and can cause socket exhaustion.
What AddWexHealthSdk handles automatically:
Concern | What the SDK does |
IHttpClientFactory wiring | Registers a named HttpClient per API |
Connection pooling | Managed by the factory — no socket exhaustion risk |
Fail-fast misconfiguration | InvalidOperationException if BaseAddress is missing |
Duplicate registrations | Safe no-op — first registration wins |
Transient DI Lifetime | A new API instance per injection - stateless and safe for concurrent use |
Logger injection | ILogger resolved from DI automatically |
The following example demonstrates how the SDK handles the IClaimsApi registration and resolution:
Program.cs
using WexHealth.Api;
using WexHealth.DependencyInjection;// Register your auth handler before AddWexHealthSdkbuilder.Services.AddTransient<OAuthTokenHandler>();builder.Services.AddWexHealthSdk(sdk =>{ sdk.AddApi( configureClient: client => client.BaseAddress = new Uri(builder.Configuration["WexHealth:BaseUrl"]!), configureHttpClientBuilder: http => { http.AddHttpMessageHandler<OAuthTokenHandler>(); }, configureResilience: polly => { polly.AddRetry(new Polly.Retry.RetryStrategyOptions<HttpResponseMessage> { MaxRetryAttempts = 3, BackoffType = Polly.DelayBackoffType.Exponential, Delay = TimeSpan.FromSeconds(1) }); }); // Register every additional API your service uses the same way sdk.AddApi<IHealthApi, HealthApi>( configureClient: client => client.BaseAddress = new Uri(builder.Configuration["WexHealth:BaseUrl"]!), configureHttpClientBuilder: http => { http.AddHttpMessageHandler<OAuthTokenHandler>(); });});After registration, inject IClaimsApi anywhere in your application — controllers, services, background workers:
public class ClaimsService(IClaimsApi api){ public Task GetClaimAsync(Guid personId, string claimNumber, CancellationToken ct)=> api.GetClaimAsync(personId, claimNumber, cancellationToken: ct);}That is the entirety of the integration code your team owns.
appsettings.json — set the base URL here (never put secrets in this file):
{
"WexHealth": {
"BaseUrl": "https://platform.uat.benefits.wexglobal.com"
}
}Step 3 — Authenticate
The API uses OAuth 2.0 Client Credentials for machine-to-machine (M2M) authentication. Choose the integration path that fits your service architecture:
Path 1: Environment variables — quick start. The SDK auto-detects these and handles token acquisition, caching, and thread-safe refresh 60 seconds before expiry:
export WEX_OAUTH2_TOKEN_URL="https://your-auth-server.com/oauth/token"
export WEX_OAUTH2_CLIENT_ID="your-client-id"
export WEX_OAUTH2_CLIENT_SECRET="your-client-secret"Path 2: DelegatingHandler — recommended for production. Your service owns the token lifecycle and secrets. Plug it in via the DI builder:
public interface ITokenProvider
{
Task<string> GetAccessTokenAsync(CancellationToken ct);
}
// Skeleton implementation — fill in your secrets manager and token endpoint calls
public class MyTokenProvider : ITokenProvider
{
private string? cachedToken;
private DateTime tokenExpiry = DateTime.MinValue;
private readonly SemaphoreSlim sync = new(1, 1);
public async Task GetAccessTokenAsync(CancellationToken ct)
{
if (cachedToken != null && DateTime.UtcNow < tokenExpiry.AddSeconds(-60))
return cachedToken;
await sync.WaitAsync(ct);
try
{
if (cachedToken != null && DateTime.UtcNow < tokenExpiry.AddSeconds(-60))
return cachedToken;
// Fetch credentials from your secrets manager, request a new token,
// then set cachedToken and tokenExpiry from the response.
return cachedToken!;
}
finally { sync.Release(); }
}
}
public class OAuthTokenHandler : DelegatingHandler
{
private readonly ITokenProvider tokenProvider;
public OAuthTokenHandler(ITokenProvider provider) => tokenProvider = provider;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken ct)
{
var token = await tokenProvider.GetAccessTokenAsync(ct);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, ct);
}
}Register both before AddWexHealthSdk. ITokenProvider is a singleton so the token cache is shared; OAuthTokenHandler is transient because DelegatingHandler instances are short-lived:
builder.Services.AddSingleton<ITokenProvider, MyTokenProvider>();builder.Services.AddTransient<OAuthTokenHandler>();Secrets (Client ID, Client Secret, Token URL) belong outside appsettings.json: use environment variables or a secrets manager (Azure Key Vault, AWS Secrets Manager) in deployed environments.
Step 4 — Call APIs
Every parameter is named, typed, and optional where the spec says so:
// Get a single claimClaimDTO claim = await api.GetClaimAsync( personId: Guid.Parse("550e8400-e29b-41d4-a716-446655440000"), claimNumber: "11BPEB1210513P0000101", xRequestId: "my-correlation-id", // optional — see below cancellationToken: ct);// Get all claims for a participant (paginated)ClaimsDTO page = await api.GetClaimsForParticipantAsync( personId: personId, pageSize: 25, index: 0, cancellationToken: ct);// page.Items → list of ClaimDTO// page.HasNext → bool// page.Index → current page index// page.PageSize → page size usedCorrelation IDs — pass your trace ID as xRequestId to correlate WEX platform logs with your own. Accepts up to 36 alphanumeric/hyphen characters:
await api.GetClaimAsync(personId, claimNumber,
xRequestId: Activity.Current?.TraceId.ToString());Step 5 — Handle Errors
The SDK throws an ApiException for any non-2xx response. The error body follows RFC 7807: Problem Details for HTTP APIs — the same standard used natively by ASP.NET Core for consistent error reporting:
try
{
var claim = await api.GetClaimAsync(personId, claimNumber, cancellationToken: ct);
return Ok(claim);
}
catch (ApiException ex) when (ex.ErrorCode == 404) { return NotFound(); }
catch (ApiException ex)
{
return StatusCode(ex.ErrorCode,
new { error = ex.Message, detail = ex.ErrorContent });
}See the Retry Contract table for which status codes are retried automatically before the exception reaches your code.
Step 6 — Override Retry (optional)
The defaults (3 retries, exponential backoff with jitter, 30 s global timeout) cover the majority of cases. Override when you need different settings:
Option A — Per-API via configureResilience (Preferred)
Scoped to one API client, does not affect others:
sdk.AddApi( configureClient: client => client.BaseAddress = new Uri(builder.Configuration["WexHealth:BaseUrl"]!), configureHttpClientBuilder: http => { http.AddHttpMessageHandler(); }, configureResilience: polly => { polly.AddTimeout(TimeSpan.FromSeconds(60)); polly.AddRetry(new Polly.Retry.RetryStrategyOptions { MaxRetryAttempts = 5, BackoffType = Polly.DelayBackoffType.Exponential, Delay = TimeSpan.FromSeconds(1) }); });Option B — Global via RetryConfiguration.HttpPipeline
Replaces the pipeline for every SDK client in the process. Use only when you want a uniform policy across all APIs and have not set configureResilience on any AddApi call:
// ⚠️ Global — affects IClaimsApi, IHealthApi, and every other registered SDK clientRetryConfiguration.HttpPipeline = RetryConfiguration.CreatePipeline( maxRetries: 5, timeoutSeconds: 60); Step 7 — Logging
ILogger<T> is resolved from DI automatically — no extra wiring needed. Control verbosity per SDK namespace in appsettings.json:
{
"Logging": {
"LogLevel": {
"WexHealth": "Information",
"WexHealth.Client.RetryConfiguration": "Warning"
}
}
}Log Category | What is logged |
WexHealth.Api.ClaimsApi | Method call start, completion, HTTP status code |
WexHealth.Client.ApiClient | HTTP request / response details |
WexHealth.Client.RetryConfiguration | Retry attempt number, delay, reason, sanitized URI |
Never log raw Authorization headers, token values, or response bodies containing participant data. Use Information or Warning in production — never Trace.
Step 8 — Health Check
Wire the SDK's liveness endpoint into ASP.NET Core health checks to surface WEX platform availability in your own /health probe:
builder.Services.AddHealthChecks() .AddCheck<WexHealthCheck>("wexhealth-api");app.MapHealthChecks("/health");public class WexHealthCheck(IHealthApi healthApi) : IHealthCheck{ public async Task<HealthCheckResult> CheckHealthAsync( HealthCheckContext context, CancellationToken ct) { try { await healthApi.GetLivenessAsync(cancellationToken: ct); return HealthCheckResult.Healthy(); } catch (Exception ex) { return HealthCheckResult.Unhealthy("WexHealth unreachable", ex);
}
}
}
On this page
- .NET