Blog Integrations & AI
PAYMENTS

Payment Gateway Integration with Razorpay in .NET Core

A working Razorpay integration in a .NET Core app, end to end — order creation, checkout, signature verification, and handling the webhook that confirms a real payment.

Apr 17, 2023

Razorpay is one of the most common payment gateways we wire into .NET Core apps for clients doing business in India, and the integration pattern is the same regardless of the product: create an order on the server, let Razorpay Checkout collect payment details on the client, then — this is the part people skip and regret — verify the payment signature on the server before you ever mark an order as paid.

Creating an Order Server-Side

Every Razorpay payment starts with an order created through the Orders API, using your key ID and key secret from the Razorpay dashboard. The order amount is always in the smallest currency unit — paise for INR, so 500 rupees is 50000. Use the official Razorpay.NET package rather than hand-rolling the HTTP calls.

csharp
using Razorpay.Api;

public class RazorpayService
{
    private readonly RazorpayClient _client;

    public RazorpayService(IConfiguration config)
    {
        _client = new RazorpayClient(
            config["Razorpay:KeyId"],
            config["Razorpay:KeySecret"]);
    }

    public Order CreateOrder(decimal amountInRupees, string receiptId)
    {
        var options = new Dictionary<string, object>
        {
            { "amount", (int)(amountInRupees * 100) },
            { "currency", "INR" },
            { "receipt", receiptId },
            { "payment_capture", 1 }
        };

        return _client.Order.Create(options);
    }
}

payment_capture set to 1 tells Razorpay to auto-capture the payment as soon as authorization succeeds, rather than leaving it authorized and requiring a separate capture call — the right default for most checkout flows. Return the order id from this endpoint to the client; that is the only thing the frontend needs to open Checkout.

Opening Checkout on the Client

The frontend loads Razorpay's checkout.js and passes the order_id you just created along with the public key id (never the key secret).

javascript
const options = {
    key: "rzp_test_yourKeyId",
    order_id: serverOrder.id,
    amount: serverOrder.amount,
    currency: "INR",
    name: "Xtreme Thoughts Softtech",
    handler: function (response) {
        fetch("/api/payments/verify", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                razorpay_order_id: response.razorpay_order_id,
                razorpay_payment_id: response.razorpay_payment_id,
                razorpay_signature: response.razorpay_signature
            })
        });
    }
};

const rzp = new Razorpay(options);
rzp.open();

Verifying the Signature Server-Side

The handler callback firing does not mean the payment is trustworthy — it runs entirely in the browser and a response object is trivial to fake from dev tools. The only proof that Razorpay actually processed this payment is the signature, an HMAC SHA256 hash of the order id and payment id joined by a pipe, computed using your key secret. Recompute it yourself and compare before touching your order status.

csharp
using System.Security.Cryptography;
using System.Text;

public bool VerifySignature(
    string orderId, string paymentId, string signature, string keySecret)
{
    var payload = $"{orderId}|{paymentId}";
    var keyBytes = Encoding.UTF8.GetBytes(keySecret);
    var payloadBytes = Encoding.UTF8.GetBytes(payload);

    using var hmac = new HMACSHA256(keyBytes);
    var hash = hmac.ComputeHash(payloadBytes);
    var generatedSignature = Convert.ToHexString(hash).ToLowerInvariant();

    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(generatedSignature),
        Encoding.UTF8.GetBytes(signature));
}

Only mark the order as paid in your database after this returns true. Use FixedTimeEquals rather than a plain string comparison — a naive equality comparison leaks timing information about how many leading characters matched, which is a real (if obscure) side channel for anyone trying to forge a signature.

Handling Webhooks for Async Confirmation

The client-side flow can fail to report back — a closed tab, a network drop right after payment succeeds — leaving your order stuck as unpaid even though Razorpay actually captured the money. Configure a webhook in the Razorpay dashboard for the payment.captured event so your server hears about it independently. Webhook payloads carry their own signature in the X-Razorpay-Signature header, verified the same HMAC way but against the raw request body and your webhook secret (not your key secret).

  • Always verify the payment signature server-side — never trust the client-side handler callback alone.
  • Amounts are always in the smallest currency unit (paise for INR) on both order creation and the amount Checkout displays.
  • Configure the payment.captured and payment.failed webhooks so orders reconcile even when the browser flow is interrupted.
  • Store the Razorpay order id, payment id, and signature against your own order record for auditing and dispute handling.
  • Use test mode keys throughout development — Razorpay provides test card numbers that simulate both success and failure.

Make webhook handling idempotent. Razorpay can and does retry a webhook delivery if your endpoint does not respond with a 200 quickly enough, so check whether you have already processed that payment id before applying the update a second time.

Wrapping Up

The parts that actually matter here are the two things easy to skip under deadline pressure: verifying the signature before trusting a payment, and having a webhook as a backstop for whenever the client-side flow does not complete cleanly. Get those two right and the rest of the integration is mostly plumbing.

Razorpay .NET Core Payments

More from the blog

View all articles

Read something that fits your problem?

Tell us what you're building. We'll come back with an honest, scoped recommendation — not a sales pitch.

Start a Project