• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » WordPress Development Recipe: Secure token-based API authentication for Stripe Payment webhook in custom plugins

WordPress Development Recipe: Secure token-based API authentication for Stripe Payment webhook in custom plugins

Securing Stripe Webhooks with Token-Based Authentication in WordPress

When integrating Stripe into a custom WordPress plugin, handling webhooks securely is paramount. Webhooks are essential for receiving real-time notifications about events like successful payments, disputes, and subscription changes. However, they are also a potential attack vector. This recipe details a robust method for securing your Stripe webhook endpoint using a shared secret token, ensuring that only legitimate Stripe requests are processed by your WordPress site.

Generating and Storing the Secret Token

The first step is to generate a strong, unique secret token. This token will be shared between your Stripe account and your WordPress plugin. It’s crucial to keep this token confidential. For development and testing, you can store it in your theme’s `functions.php` or a custom plugin’s main file. For production, a more secure method is to use environment variables or a dedicated configuration file outside the webroot.

Here’s how you can generate a token and store it in your plugin’s main file (e.g., `my-stripe-plugin.php`):

/**
 * My Stripe Plugin Main File
 *
 * @package MyStripePlugin
 */

// Define the secret token. In production, use environment variables or a secure config.
define( 'MY_STRIPE_WEBHOOK_SECRET', 'sk_test_YOUR_VERY_STRONG_SECRET_TOKEN_HERE' );

// ... rest of your plugin code

Replace sk_test_YOUR_VERY_STRONG_SECRET_TOKEN_HERE with a randomly generated, strong secret. You can use tools like OpenSSL or online password generators to create a secure token. This token will be used later in your Stripe dashboard.

Configuring the Webhook in Stripe

Navigate to your Stripe Dashboard. Go to Developers > Webhooks. Click “Add endpoint”.

  • Endpoint URL: This should be the URL of your WordPress site where your webhook handler is located. For example: https://your-wordpress-site.com/wp-json/my-stripe-plugin/v1/webhook. Ensure this URL is publicly accessible.
  • Events to send: Select the events you want to listen for. For a basic payment setup, you’ll likely want payment_intent.succeeded, charge.succeeded, and potentially others like checkout.session.completed.

Crucially, after saving the endpoint, Stripe will display a “Signing secret”. This is the secret you’ll use to verify incoming requests. Copy this secret. It will look something like whsec_....

Implementing the Webhook Handler in WordPress

WordPress’s REST API is the ideal place to register your webhook endpoint. This provides a structured and secure way to handle incoming requests. We’ll create a custom endpoint that listens for POST requests.

Add the following code to your plugin’s main file or a dedicated include file:

 'POST',
        'callback' => 'my_stripe_handle_webhook',
        'permission_callback' => '__return_true', // Permissions handled within the callback
    ) );
}
add_action( 'rest_api_init', 'my_stripe_register_webhook_endpoint' );

/**
 * Handles incoming Stripe webhook requests.
 *
 * @param WP_REST_Request $request The incoming request object.
 * @return WP_REST_Response|\WP_Error Response object or WP_Error.
 */
function my_stripe_handle_webhook( WP_REST_Request $request ) {
    // 1. Get the raw POST data and the signature header.
    $payload = $request->get_body();
    $signature = $request->get_header( 'stripe-signature' );

    // Ensure we have a signature.
    if ( ! $signature ) {
        return new WP_Error( 'stripe_webhook_error', 'Stripe-Signature header missing.', array( 'status' => 400 ) );
    }

    // 2. Verify the signature using the Stripe PHP SDK.
    // Ensure you have the Stripe PHP SDK installed via Composer or manually.
    // If using Composer, include the autoloader: require_once MY_STRIPE_PLUGIN_PATH . 'vendor/autoload.php';
    // For simplicity here, we assume the SDK is available.
    try {
        // Retrieve the webhook secret from your defined constant.
        // In production, this should be fetched securely.
        $webhook_secret = MY_STRIPE_WEBHOOK_SECRET;

        // Use the Stripe PHP SDK to verify the signature.
        // Make sure to use the correct webhook secret from your Stripe dashboard.
        \Stripe\Stripe::setApiKey( MY_STRIPE_SECRET_KEY ); // Your Stripe API key (publishable or secret)
        $event = \Stripe\Webhook::constructEvent(
            $payload, $signature, $webhook_secret
        );
    } catch ( \UnexpectedValueException $e ) {
        // Invalid payload
        return new WP_Error( 'stripe_webhook_error', 'Invalid payload.', array( 'status' => 400 ) );
    } catch ( \Stripe\Exception\SignatureVerificationException $e ) {
        // Invalid signature
        return new WP_Error( 'stripe_webhook_error', 'Invalid signature.', array( 'status' => 400 ) );
    } catch ( \Exception $e ) {
        // Other errors
        return new WP_Error( 'stripe_webhook_error', 'Webhook error: ' . $e->getMessage(), array( 'status' => 500 ) );
    }

    // 3. Process the event based on its type.
    // The $event object contains the event type and data.
    $event_data = $event->data->object;

    switch ( $event['type'] ) {
        case 'payment_intent.succeeded':
            // Handle successful payment intent.
            // $event_data will contain the PaymentIntent object.
            my_stripe_process_payment_intent_succeeded( $event_data );
            break;
        case 'charge.succeeded':
            // Handle successful charge.
            // $event_data will contain the Charge object.
            my_stripe_process_charge_succeeded( $event_data );
            break;
        // ... handle other event types as needed
        default:
            // Unexpected event type.
            error_log( 'Received unknown Stripe webhook event type: ' . $event['type'] );
            break;
    }

    // 4. Return a 200 OK response to acknowledge receipt of the event.
    return new WP_REST_Response( array( 'success' => true ), 200 );
}

/**
 * Placeholder function for processing payment_intent.succeeded event.
 *
 * @param object $payment_intent The PaymentIntent object from Stripe.
 */
function my_stripe_process_payment_intent_succeeded( $payment_intent ) {
    // Implement your logic here.
    // For example, update order status, send confirmation emails, etc.
    // Access details like $payment_intent->id, $payment_intent->amount, $payment_intent->metadata.
    error_log( 'Stripe Webhook: payment_intent.succeeded - ID: ' . $payment_intent->id );
    // Example: Update order status in your custom order system.
    // update_order_status( $payment_intent->metadata->order_id, 'completed' );
}

/**
 * Placeholder function for processing charge.succeeded event.
 *
 * @param object $charge The Charge object from Stripe.
 */
function my_stripe_process_charge_succeeded( $charge ) {
    // Implement your logic here.
    // This event is often redundant if you're processing PaymentIntents,
    // but can be useful for older integrations or specific use cases.
    error_log( 'Stripe Webhook: charge.succeeded - ID: ' . $charge->id );
    // Example: If you need to access the charge ID directly.
    // update_payment_record( $charge->id, 'success' );
}

// Ensure MY_STRIPE_SECRET_KEY is defined elsewhere in your plugin for Stripe SDK initialization.
// For example: define( 'MY_STRIPE_SECRET_KEY', 'sk_test_YOUR_STRIPE_SECRET_KEY' );

Integrating the Stripe PHP SDK

The code above relies on the Stripe PHP SDK to verify the webhook signature. You have two primary ways to include it:

  • Composer: This is the recommended method for modern PHP development. If your plugin uses Composer, ensure you have added the Stripe SDK as a dependency: composer require stripe/stripe-php. Then, include the autoloader in your plugin’s main file: require_once MY_STRIPE_PLUGIN_PATH . 'vendor/autoload.php';.
  • Manual Inclusion: Download the Stripe PHP library and include the necessary files manually. This is less maintainable and not recommended for production environments.

Make sure to define your Stripe secret API key (e.g., MY_STRIPE_SECRET_KEY) elsewhere in your plugin for the SDK to initialize correctly.

Configuring the Webhook Secret in Stripe Dashboard

Now, let’s connect the WordPress secret token with the Stripe dashboard. In your Stripe Dashboard, navigate back to Developers > Webhooks. Edit the endpoint you created earlier.

  • Under “Signing secret”, you’ll see the secret key that Stripe generated for this endpoint (e.g., whsec_...).
  • Copy this whsec_... secret.
  • Paste this copied secret into your WordPress plugin’s code where MY_STRIPE_WEBHOOK_SECRET is defined. For example: define( 'MY_STRIPE_WEBHOOK_SECRET', 'whsec_YOUR_STRIPE_GENERATED_SECRET_HERE' );

Important: The secret you define in your WordPress code (MY_STRIPE_WEBHOOK_SECRET) must match the “Signing secret” provided by Stripe for that specific webhook endpoint. This is the crucial link that allows Stripe to sign requests and your plugin to verify them.

Testing Your Webhook Implementation

Thorough testing is essential. You can use the Stripe CLI for local testing, which is highly recommended.

Using the Stripe CLI

1. **Install Stripe CLI:** Follow the official Stripe documentation to install the CLI. 2. **Login:** Run stripe login to connect the CLI to your Stripe account. 3. **Forward Events:** In your terminal, navigate to your WordPress project directory and run:

stripe listen --forward-to localhost:8000/wp-json/my-stripe-plugin/v1/webhook

Note: If you’re running WordPress locally using its built-in server (php -S localhost:8000), the above command will work. If you’re using a different local development environment (like MAMP, XAMPP, Local by Flywheel), you’ll need to adjust the localhost:PORT to match your setup and ensure the endpoint is accessible. You might need to expose your local server to the internet using tools like ngrok if you’re testing with a live Stripe account and not just the CLI.

The Stripe CLI will output a webhook signing secret (e.g., whsec_...). Use this secret in your WordPress code for MY_STRIPE_WEBHOOK_SECRET during testing. The CLI will then forward events from your Stripe account to your local development server.

Manual Testing via Stripe Dashboard

You can also trigger test events directly from the Stripe Dashboard:

  • Go to Developers > Events.
  • Click “Create test event”.
  • Select the event type you want to test (e.g., payment_intent.succeeded).
  • Configure the event details if necessary.
  • Click “Create event”.

Check your WordPress debug log (if enabled) or use error_log() statements within your webhook handler to confirm that the event was received and processed correctly.

Production Considerations

For a production environment, several enhancements are recommended:

  • Secure Token Storage: Never hardcode sensitive secrets directly in your plugin files. Use environment variables (e.g., via a .env file and a library like `vlucas/phpdotenv`) or a secure configuration management system.
  • Error Logging: Implement robust error logging. Ensure that any failures in webhook processing are logged with sufficient detail to aid debugging.
  • Idempotency: Design your webhook handlers to be idempotent. This means that processing the same event multiple times should not have unintended side effects. Stripe may occasionally resend events.
  • Rate Limiting and Security Headers: While Stripe’s signature verification is the primary security measure, consider additional security layers like IP whitelisting (if feasible and Stripe’s IP ranges are stable) or rate limiting on your REST API endpoint to mitigate brute-force attacks.
  • HTTPS: Ensure your WordPress site is served over HTTPS. Stripe requires this for webhook endpoints.

By implementing this token-based authentication, you significantly enhance the security of your Stripe webhook integration, ensuring that your WordPress site only responds to legitimate notifications from Stripe.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • How to implement custom REST API Controllers endpoints with token authentication in Gutenberg blocks
  • Step-by-Step Guide to building a custom real-time activity logs block for Gutenberg using PHP block-render callbacks
  • How to implement custom WordPress Database Class ($wpdb) endpoints with token authentication in Gutenberg blocks
  • Building custom automated PDF financial reports and invoices for WooCommerce using custom PHP-Spreadsheet exports
  • WordPress Development Recipe: Leveraging Readonly classes to build type-safe, auto-wired hooks

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (658)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (872)
  • PHP (5)
  • PHP Development (41)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (41)
  • WordPress Plugin Development (43)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • How to implement custom REST API Controllers endpoints with token authentication in Gutenberg blocks
  • Step-by-Step Guide to building a custom real-time activity logs block for Gutenberg using PHP block-render callbacks
  • How to implement custom WordPress Database Class ($wpdb) endpoints with token authentication in Gutenberg blocks

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (872)
  • Debugging & Troubleshooting (658)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala