Stripe Webhook Example C# for ASP.NET

Author: Jameson Richman Expert

Published On: 2025-11-08

Prepared by Jameson Richman and our team of experts with over a decade of experience in cryptocurrency and digital asset analysis. Learn more about us.

Summary: This article provides a comprehensive, practical guide to implementing a stripe webhook example c# in an ASP.NET Core application. You’ll learn how Stripe webhooks work, how to securely verify and handle webhook events in C#, robust patterns for idempotency and retry handling, testing tips with the Stripe CLI and ngrok, deployment best practices, and sample code you can copy and adapt for production.

What is a webhook and why Stripe uses them?

A webhook is an HTTP callback: Stripe sends HTTP POST requests to your endpoint to notify your application about asynchronous events (payments, disputes, subscriptions, checkout sessions, etc.). Webhooks let your server react in near real-time to events that happen in Stripe without polling the API.

For the official Stripe webhook documentation, see the Stripe docs: Stripe Webhooks Documentation. For a general overview of webhooks, see the Wikipedia page on webhooks: Webhook (Wikipedia).

How Stripe webhooks work (high level)

  • Stripe generates events (e.g., charge.succeeded, invoice.payment_failed) when things happen.
  • Stripe sends the event payload as an HTTPS POST to the URL you registered as a webhook endpoint.
  • Your endpoint should verify the payload using the signing secret and then process the event (update database, send emails, fulfill orders, etc.).
  • Your endpoint must respond with a 2xx status code to acknowledge successful handling. Non-2xx responses trigger Stripe retries with exponential backoff.

Prerequisites for a Stripe webhook example C#

  • .NET 6+ or .NET Core 3.1/5 with ASP.NET Core (examples use ASP.NET Core style controllers/middleware)
  • Stripe account and a webhook signing secret (from Dashboard or Stripe CLI)
  • Stripe.NET NuGet package (Stripe.net)
  • ngrok or Stripe CLI for local testing
  • Secure storage for API keys and signing secrets (Azure Key Vault, environment variables, or similar)

Install Stripe.NET and configure project

Install the Stripe.NET library from NuGet:

dotnet add package Stripe.net

Set your Stripe API secret key in environment variables or a secure secrets store. In appsettings or startup, you can configure the Stripe API key:

StripeConfiguration.ApiKey = Environment.GetEnvironmentVariable("STRIPE_SECRET_KEY");

Minimal Stripe webhook example C# (ASP.NET Core)

This controller demonstrates how to receive and verify Stripe webhooks using the official Stripe.net helper:

// WebhookController.cs
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Stripe;
using Stripe.Checkout;

[ApiController]
[Route("api/[controller]")]
public class WebhookController : ControllerBase
{
    // Set your endpoint's signing secret as an environment variable
    private readonly string _stripeWebhookSecret = Environment.GetEnvironmentVariable("STRIPE_WEBHOOK_SECRET");

    [HttpPost]
    public async Task Post()
    {
        // Read the request body
        var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();

        // Get Stripe signature header
        var stripeSignature = Request.Headers["Stripe-Signature"];

        Event stripeEvent;

        try
        {
            // Verify signature and construct event
            stripeEvent = EventUtility.ConstructEvent(json, stripeSignature, _stripeWebhookSecret);
        }
        catch (StripeException e)
        {
            // Invalid signature
            return BadRequest($"Webhook error: {e.Message}");
        }

        // Handle the event
        switch (stripeEvent.Type)
        {
            case Events.CheckoutSessionCompleted:
                var session = stripeEvent.Data.Object as Session;
                // TODO: fulfill order, mark payment complete, etc.
                break;

            case Events.PaymentIntentSucceeded:
                var paymentIntent = stripeEvent.Data.Object as PaymentIntent;
                // TODO: update invoice, send receipt, etc.
                break;

            case Events.InvoicePaymentFailed:
                var invoice = stripeEvent.Data.Object as Invoice;
                // TODO: notify customer, retry collection flow
                break;

            // Add additional event types you care about
            default:
                // Unexpected event type
                break;
        }

        // Return 200 to acknowledge receipt of the event
        return Ok();
    }
}

Notes on the example

  • Always verify the signature header (Stripe-Signature) using the webhook signing secret. Do not skip verification in production.
  • Use environment variables or a secrets manager to store STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET.
  • Keep your endpoint route simple and protected by HTTPS in production.

Detailed explanation: verifying signatures and ConstructEvent

The recommended way to verify webhook payloads is EventUtility.ConstructEvent from the Stripe.NET library. It validates that the payload was signed by Stripe using your webhook signing secret and prevents replay attacks (it also checks the timestamp and tolerance window).

Stripe sends the signature in the Stripe-Signature header. ConstructEvent throws a StripeException if the signature is invalid.

Handling common Stripe events in C#

Here are actionable patterns for common event types:

  • checkout.session.completed - Use this to fulfill orders when Checkout is used. The Data.Object is a Session. Use session.PaymentIntentId or client_reference_id to link to your order.
  • payment_intent.succeeded - Good for direct PaymentIntent confirmation flows.
  • invoice.payment_succeeded - Useful for subscriptions and recurring billing.
  • charge.failed / invoice.payment_failed - Trigger notifications and retry logic for customers.

Idempotency and duplicate webhooks

Stripe may retry events; therefore, your webhook handler must be idempotent. Use these strategies:

  1. Persist Stripe event.id (e.g., evt_1Jxyz...) and ignore duplicate processing for the same event.id.
  2. Use database transactions when updating state and create unique constraints on event IDs to avoid double-processing.
  3. For long-running work, enqueue a background job (e.g., using Hangfire, Azure Queue, or RabbitMQ) and respond 200 quickly so Stripe won’t keep retrying.

Testing webhooks locally with Stripe CLI and ngrok

Local development must expose a publicly reachable URL. Two common approaches:

  • Stripe CLI - Recommended. The Stripe CLI can forward events directly to your local machine and can send test events. Example:
stripe listen --forward-to localhost:5000/api/webhook
stripe trigger checkout.session.completed

See Stripe CLI docs at Stripe CLI.

  • ngrok - Forward local port to the internet:
ngrok http 5000
# Then set webhook URL in Stripe Dashboard to https://.ngrok.io/api/webhook

Security best practices

  • Always verify the signature using your webhook signing secret (do not accept unsigned payloads).
  • Do not rely on source IP addresses for validation (Stripe IPs can change).
  • Protect API keys and webhook secrets in a secrets manager such as Azure Key Vault, AWS Secrets Manager, or environment variables with restricted access.
  • Rotate webhook signing secrets periodically. When rotating, keep both the old and new secret active while migrating.
  • Use HTTPS on public endpoints (should be enforced by ASP.NET Core hosting or reverse proxy).

Error handling and retry behavior

Stripe retries webhook events with exponential backoff if it does not receive a 2xx response. Your webhook should:

  • Return 2xx only after you have safely queued or completed the necessary processing.
  • Aim to respond quickly and offload heavy work to background processors.
  • Record the status of event processing so retries can be ignored if the work was already completed.

Advanced C# patterns: background processing and queues

To avoid timeouts and long-running HTTP responses, offload heavy tasks to a background queue:

public IActionResult Post()
{
    var json = await new StreamReader(Request.Body).ReadToEndAsync();
    var stripeEvent = EventUtility.ConstructEvent(json, Request.Headers["Stripe-Signature"], _webhookSecret);

    // Persist event id to prevent duplicates
    if (EventAlreadyProcessed(stripeEvent.Id)) return Ok();

    // Enqueue for background processing (e.g., a job queue)
    await _backgroundJobClient.EnqueueAsync(() => ProcessStripeEvent(stripeEvent));

    return Ok();
}

This pattern ensures you acknowledge Stripe quickly and then process reliably in the background. Use transaction-safe storage to record event receipt.

Logging, monitoring, and observability

Monitor webhook activity from multiple angles:

  • Stripe Dashboard: view recent webhook delivery attempts and logs
  • Application logs: log event IDs, types, and processing outcomes
  • Alerting: configure alerts for repeated failures or high error rates
  • Tracing: add distributed tracing (OpenTelemetry) to track workflows across web requests and background jobs

Troubleshooting common issues

  • Invalid signature errors — Verify you’re using the correct signing secret (found in the Stripe Dashboard under the Webhooks section or via the Stripe CLI). Ensure the raw request body is passed to ConstructEvent without modifications (don’t pre-process JSON).
  • Timeouts / 500 errors — Respond quickly (200 OK) after queueing work. Check application logs for exceptions.
  • Duplicate processing — Persist event IDs to ensure idempotency and implement unique database constraints on the event ID column.
  • Missing fields — Ensure you’re casting the Data.Object to the expected type (Session, PaymentIntent, Invoice) and check for nulls.

Production deployment considerations

  • Use HTTPS and valid certificates for webhook endpoints.
  • Store secrets in a secure store (Azure Key Vault, AWS Secrets Manager).
  • Consider using dedicated webhook-processing services or serverless functions (AWS Lambda, Azure Functions) if it fits your architecture.
  • Set up multiple webhook endpoints if you need distinct processing behaviors for different event types.
  • Track delivery attempts in the Stripe Dashboard and set up alerts for repeated failures.

Sample: Full Checkout Fulfillment Example (C#)

// Simplified example that validates, persists event id, and fulfills order:
[HttpPost]
public async Task Post()
{
    var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
    var stripeSignature = Request.Headers["Stripe-Signature"];
    Event stripeEvent;

    try
    {
        stripeEvent = EventUtility.ConstructEvent(json, stripeSignature, _webhookSecret);
    }
    catch (StripeException)
    {
        return BadRequest();
    }

    // Idempotency: record event id
    if (await _dbContext.StripeEvents.AnyAsync(e => e.EventId == stripeEvent.Id))
    {
        return Ok();
    }

    await _dbContext.StripeEvents.AddAsync(new StripeEventRecord { EventId = stripeEvent.Id, Type = stripeEvent.Type, ReceivedAt = DateTime.UtcNow });
    await _dbContext.SaveChangesAsync();

    if (stripeEvent.Type == Events.CheckoutSessionCompleted)
    {
        var session = stripeEvent.Data.Object as Session;

        // Example: find order by client_reference_id and mark as paid
        var order = await _dbContext.Orders.SingleOrDefaultAsync(o => o.Reference == session.ClientReferenceId);
        if (order != null)
        {
            order.Status = "Paid";
            await _dbContext.SaveChangesAsync();
            // Optionally send receipt/email and trigger fulfillment
        }
    }

    return Ok();
}

Testing checklist before going live

  • Verify webhook signing secret and API keys are correct and stored securely.
  • Test with Stripe CLI: simulate events you will handle.
  • Ensure idempotency by resending the same event and confirming it’s not re-processed.
  • Monitor Stripe Dashboard for delivery success and failures.
  • Load test background consumer if high volume is expected.

Advanced topics and patterns

  • Webhook multiplexer: use one endpoint that routes to internal services based on event type.
  • Queueing and batching: aggregate multiple events for the same order into a single background job to reduce DB contention.
  • Secret rotation: implement dual secret verification during rotation windows.
  • Graceful degradation: if your processing system is down, accept events and store raw payloads for later replay.

Resources and references

Related reads and external backlinks

For related trading and platform articles you might find useful, see these analyses and guides:

Useful crypto exchange links (optional)

If you are integrating payment flows that interact with crypto trading platforms or offering links for user signups, here are several resources:

Checklist: Going live with your Stripe webhook

  • Confirm webhook URL is accessible via HTTPS in production.
  • Store all secrets securely; do not hardcode them.
  • Verify webhook signatures on every request.
  • Implement idempotency (store event IDs).
  • Queue long-running tasks and respond quickly to Stripe.
  • Monitor Stripe Dashboard and application logs for failures.
  • Test thoroughly using the Stripe CLI and replay events as needed.

Conclusion

Implementing a robust stripe webhook example c# requires correct signature verification, idempotency, quick responses, and reliable background processing. Using the Stripe.NET library simplifies verification via EventUtility.ConstructEvent, and adopting patterns like persistent event recording and queueing background jobs will make your webhook endpoint resilient under production load. Follow the security and best-practice recommendations above, test with the Stripe CLI or ngrok, and monitor delivery in the Stripe Dashboard to ensure reliable handling of payment events.

If you need a tailored webhook implementation for a specific use case (subscriptions, marketplace payouts, or checkout integrations), tell me about your architecture and I can provide a custom code example and deployment checklist.

Other Crypto Signals Articles