Accepting cryptocurrency is no longer a fringe feature. Whether you run a SaaS product, an online store, or a donations platform, adding a crypto payment system in .NET gives you lower fees, instant settlement, and access to customers who prefer Bitcoin, Ethereum, or stablecoins over cards. This guide walks through the architecture and the concrete C# steps to accept crypto payments in an ASP.NET Core application — from choosing a gateway to verifying webhooks and handling price volatility.
The examples target .NET 9 and .NET 8, the two currently supported releases (.NET 10 arrives as the next LTS in November 2026), but the patterns apply to any modern ASP.NET Core stack.
Choosing between a hosted gateway and a self-hosted node
The first architectural decision is whether you integrate a hosted payment gateway or run your own blockchain node. For 99% of applications, a hosted gateway is the right answer. Running your own full node means managing wallets, private keys, confirmations, and reorg handling yourself — a serious security and operations burden that only makes sense at very large scale.
A hosted crypto payment gateway abstracts all of that behind a REST API. You create a charge, redirect the customer to a checkout page, and receive a webhook when the payment confirms on-chain. Three gateways dominate .NET integrations:
Coinbase Commerce — a well-maintained community .NET/C# library exists, supports major coins, and settles into a merchant-controlled wallet.
BitPay — one of the oldest and most established processors, with an official C# .NET library and strong compliance and fraud tooling.
NOWPayments — supports 50+ cryptocurrencies with automatic conversion to stablecoins or fiat, which is useful for managing volatility.
For this guide we'll model the integration around a Coinbase Commerce-style flow, because the create-charge-then-webhook pattern is nearly identical across all three providers.
The payment flow at a glance
A robust crypto payment system follows the same lifecycle regardless of gateway:
The customer chooses "Pay with crypto" at checkout.
Your server calls the gateway API to create a charge with the amount, currency, and an internal order ID stored in metadata.
The gateway returns a hosted checkout URL and a charge ID.
The customer pays; the gateway watches the blockchain for confirmations.
The gateway sends a webhook to your endpoint when the charge is
confirmed,failed, orexpired.Your webhook handler verifies the signature, updates the order, and fulfils it.
The golden rule: never mark an order as paid from the browser redirect. Always wait for the server-to-server webhook, because the redirect can be faked or interrupted.
Creating a charge from C#
Keep your gateway API key in configuration or a secrets manager — never in source control. In ASP.NET Core, register a typed HttpClient and build a small service to create charges.
csharp
public class CryptoPaymentService
{
private readonly HttpClient _http;
public CryptoPaymentService(HttpClient http) => _http = http;
public async Task<ChargeResponse> CreateChargeAsync(Order order)
{
var payload = new
{
name = $"Order {order.Id}",
description = order.Description,
pricing_type = "fixed_price",
local_price = new { amount = order.Total.ToString("F2"), currency = "EUR" },
metadata = new { order_id = order.Id }
};
var response = await _http.PostAsJsonAsync("charges", payload);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<ChargeResponse>();
}
}Register it in Program.cs with the base address and the API key header applied once:
csharp
builder.Services.AddHttpClient<CryptoPaymentService>(client =>
{
client.BaseAddress = new Uri("https://api.commerce.coinbase.com/");
client.DefaultRequestHeaders.Add("X-CC-Api-Key", builder.Configuration["Crypto:ApiKey"]);
client.DefaultRequestHeaders.Add("X-CC-Version", "2018-03-22");
});When the charge is created, store the returned charge ID against your order and redirect the customer to the hosted checkout URL. This keeps sensitive payment handling off your own pages and reduces your compliance surface.
Verifying the webhook — the security-critical step
The webhook is where money is confirmed, so it must be tamper-proof. Every serious gateway signs its webhook payload with an HMAC using a shared secret. Your handler must recompute the signature over the raw request body and compare it in constant time before trusting anything inside.
csharp
[ApiController]
[Route("webhooks/crypto")]
public class CryptoWebhookController : ControllerBase
{
private readonly string _sharedSecret;
private readonly IOrderService _orders;
[HttpPost]
public async Task<IActionResult> Handle()
{
using var reader = new StreamReader(Request.Body);
var rawBody = await reader.ReadToEndAsync();
var signature = Request.Headers["X-CC-Webhook-Signature"].ToString();
var expected = ComputeHmacSha256(rawBody, _sharedSecret);
if (!CryptographicOperations.FixedTimeEquals(
Convert.FromHexString(signature),
Convert.FromHexString(expected)))
return Unauthorized();
var evt = JsonSerializer.Deserialize<WebhookEvent>(rawBody);
if (evt.Type == "charge:confirmed")
await _orders.MarkPaidAsync(evt.Data.Metadata.OrderId);
return Ok();
}
}Three details matter here. Read the body raw — model binding re-serializes and breaks the signature. Use CryptographicOperations.FixedTimeEquals to avoid timing attacks. And make the handler idempotent: gateways retry webhooks, so processing the same charge:confirmed twice must not double-fulfil the order. Guard it with a unique constraint on the charge ID or an "already processed" check.
Handling volatility with stablecoins and instant conversion
Crypto prices move. If you price a €100 order and the customer pays in Bitcoin, the amount of BTC is locked at charge creation for a short window, but the value you ultimately hold can swing. There are three standard mitigations.
Price in fiat and let the gateway lock the exchange rate for the payment window — every major gateway does this automatically. Accept stablecoins such as USDC or USDT, which track the dollar and remove most short-term volatility. Or enable auto-conversion to fiat or a stablecoin at settlement, a feature NOWPayments and others offer, so what lands in your account is stable value rather than a volatile asset.
Choose based on whether you want to hold crypto as treasury or simply use it as a payment rail. Most businesses pick instant conversion for accounting simplicity.
Persisting state and reconciling
Model the payment as its own entity, not a boolean on the order. Store the charge ID, status, coin, on-chain amount, confirmation count, and timestamps. This gives you an audit trail, lets you reconcile against the gateway dashboard, and makes disputes traceable. Use Entity Framework Core with a status enum (Pending, Confirmed, Underpaid, Expired, Failed) and update it only from verified webhooks or a periodic reconciliation job that polls the gateway for charges your webhook may have missed.
Underpayments and overpayments are real edge cases in crypto — a customer may send slightly less than requested. Decide your policy in advance: auto-refund, credit the difference, or hold for manual review, and encode it explicitly.
Testing before you go live
Never test against mainnet with real funds. Use each gateway's sandbox or testnet mode, which issues test charges you can complete without spending money. Write integration tests that post signed sample webhook payloads to your endpoint and assert the order transitions correctly, including the duplicate-delivery case. Verify signature rejection by sending a deliberately corrupted payload and confirming your handler returns 401.
Before launch, run through a checklist: API keys in a secrets store, HTTPS enforced on the webhook route, signature verification active, idempotency guaranteed, volatility policy set, and refund/underpayment rules documented.
Bringing it together
A crypto payment system in .NET comes down to a clean separation of concerns: a service that creates charges, a hardened webhook controller that verifies and applies confirmations, a persistence layer that records every state change, and a volatility strategy that matches your treasury goals. Lean on a hosted gateway for the blockchain heavy lifting, keep the browser out of the trust path, and treat the webhook as the single source of truth. Get those four pieces right and you have a production-grade crypto checkout that fits naturally into any modern ASP.NET Core architecture.


