Skip to content

ASP.NET Core: AddPaylera, MapPayleraRelay, MapPayleraWebhooks

Paylera.AspNetCore plugs into the standard ASP.NET Core pipeline:

APIWhere it goes
builder.Services.AddPaylera(...)DI registration; configures the relay + typed client + CSRF + customer identity callback.
app.MapPayleraRelay("/api/paylera")Mounts the browser-safe relay routes.
app.MapPayleraWebhooks("/webhooks/paylera", ...)Mounts an inbound webhook receiver with signature verification + handler dispatch.

AddPaylera

using System.Security.Claims;
using Paylera.AspNetCore;
builder.Services.AddPaylera(opts =>
{
opts.ApiToken = builder.Configuration["Paylera:ApiToken"];
opts.ApiVersion = "2026-05-01"; // optional pin
opts.BaseUrl = null; // self-hosted override
opts.TenantId = "acme"; // namespaces auto-create idempotency keys
opts.AutoCreateCustomer = true;
opts.Csrf.Enabled = true;
opts.IdentifyCustomer = ctx => new PayleraCustomerIdentity
{
CustomerId = ctx.User.FindFirstValue("paylera_customer_id"),
Email = ctx.User.FindFirstValue(ClaimTypes.Email),
Name = ctx.User.Identity?.Name,
};
});

The options object is the single configuration surface. Validation is eager — startup throws if ApiToken is missing and BaseUrl is unset, or if IdentifyCustomer is null.

IdentifyCustomer

Runs once per relay request. The merchant resolves the authenticated caller into a PayleraCustomerIdentity:

  • CustomerId — the Paylera customer UUID when known. Look it up from your own merchant database, or pull it off a JWT claim that you set after the first signup.
  • Email, Name — used only when CustomerId is null and AutoCreateCustomer = true. The relay creates the customer using these on the first hit.

The callback signature is intentionally synchronous — DI lookups happen through ctx.RequestServices if you need them.

AutoCreateCustomer

When true (default) and IdentifyCustomer returns a null CustomerId, the relay creates the customer on Paylera before forwarding the request. The auto-create call uses an idempotency key of paylera-relay-autocreate:{TenantId}:{Email}, so concurrent first-render calls from a freshly-signed-up user collapse to one customer rather than racing.

Csrf

opts.Csrf.Enabled = true; // default
opts.Csrf.CookieName = "paylera_csrf";
opts.Csrf.HeaderName = "Paylera-CSRF-Token";
opts.Csrf.TokenLifetime = TimeSpan.FromMinutes(15);

Double-submit cookie. The browser fetches GET /csrf-token first, which mints a token, drops a Set-Cookie, and returns the value. Every state-changing relay call (POST /attach, POST /check, POST /track, POST /billing-portal, POST /subscriptions/.../upgrade, POST /subscriptions/.../cancel) must echo that value in the Paylera-CSRF-Token header; the relay verifies in constant time before forwarding.

Only turn Csrf.Enabled = false when the host pipeline already enforces CSRF — the relay refuses naked POSTs by default.

MapPayleraRelay

app.MapPayleraRelay("/api/paylera").RequireAuthorization();

Mounts the ten Paylera relay routes plus the auxiliary GET /csrf-token:

MethodPathNotes
GET/csrf-tokenMints + cookies a fresh CSRF token.
POST/attachInjects resolved customer_id; auto-stamps Idempotency-Key.
POST/checkRuntime entitlement gate; optional Idempotency-Key.
GET/check?feature_code=…UI-gate variant, no body, no CSRF.
POST/trackUses dedup_key from body as upstream key when present.
GET/entitlements?subscription_id=…Bulk entitlement read.
GET/meResolves customer; triggers auto-create when configured.
GET/plansSession-optional; query passes through.
GET/invoicesCustomer-scoped invoice history.
POST/billing-portalMints a self-service portal session.
POST/subscriptions/{id}/upgradeForwards to change-plan.
POST/subscriptions/{id}/cancelCancel-at-period-end or immediate.

The returned RouteGroupBuilder is chainable — pin auth, CORS, rate limits, OpenAPI metadata, anything that takes a route group.

Bearer token & versioning

The relay never relies on the browser to supply the bearer token, Paylera-Api-Version, or Idempotency-Key headers. Those are stamped inside the SDK pipeline so the browser only sees the relay’s surface, not the upstream Paylera surface.

Directly injected IPayleraClient

AddPaylera from Paylera.AspNetCore is a superset of AddPaylera from Paylera.Sdk: it transparently registers the typed client too. Inject it for server-side calls that don’t want the relay envelope:

app.MapGet("/admin/plans", async (IPayleraClient paylera, CancellationToken ct) =>
{
var plans = await paylera.ListPlansAsync(
product_id: null,
code: null,
status: PlanStatus.Active,
limit: 50,
cursor: null,
sort: null,
cancellationToken: ct);
return Results.Ok(plans);
});

Use the typed client for back-office endpoints, admin tools, batch jobs, and any code that lives entirely server-side. Use the relay for any call that originates in a browser.

MapPayleraWebhooks

See webhooks for the full reference. Quick example:

app.MapPayleraWebhooks("/webhooks/paylera", opts =>
{
opts.SigningSecret = builder.Configuration["Paylera:WebhookSecret"];
opts.ToleranceSeconds = 300;
opts.On<InvoicePaidEvent>("invoice.paid", async (ev, ctx) =>
{
await fulfilment.MarkPaidAsync(ev.Id, ctx.RequestAborted);
});
});

MapPayleraWebhooks reads the raw body before any middleware can consume it, verifies the Paylera-Signature HMAC, and dispatches to a typed or untyped handler — so signature integrity is independent of any later JSON re-parse.

Example app

examples/dotnet/AspNetCoreMinimalApi/ is a complete minimal-API host with JWT auth, the relay enabled, and a bundled SPA-style HTML page that exercises every route end-to-end.