ASP.NET Core: AddPaylera, MapPayleraRelay, MapPayleraWebhooks
Paylera.AspNetCore plugs into the standard ASP.NET Core pipeline:
| API | Where 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 whenCustomerIdis null andAutoCreateCustomer = 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; // defaultopts.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:
| Method | Path | Notes |
|---|---|---|
GET | /csrf-token | Mints + cookies a fresh CSRF token. |
POST | /attach | Injects resolved customer_id; auto-stamps Idempotency-Key. |
POST | /check | Runtime entitlement gate; optional Idempotency-Key. |
GET | /check?feature_code=… | UI-gate variant, no body, no CSRF. |
POST | /track | Uses dedup_key from body as upstream key when present. |
GET | /entitlements?subscription_id=… | Bulk entitlement read. |
GET | /me | Resolves customer; triggers auto-create when configured. |
GET | /plans | Session-optional; query passes through. |
GET | /invoices | Customer-scoped invoice history. |
POST | /billing-portal | Mints a self-service portal session. |
POST | /subscriptions/{id}/upgrade | Forwards to change-plan. |
POST | /subscriptions/{id}/cancel | Cancel-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.