Cashier-stripe: Verifying webhooks signatures

Created on 28 Aug 2017  路  6Comments  路  Source: laravel/cashier-stripe

Stripe offer Stripe-Signature in headers when sending webhook notification.

Instead of checking if event exists on the Stripe API, which:

  • Hit the Stripe API rate limit on your account
  • Use no necessary bandwidth
  • Can fail or timeout, thinks like that
  • Stripe API sometimes respond with failure, even if the event exists (I had this issue 4 times, so it happens).

it's possible to use this method https://stripe.com/docs/webhooks#signatures

Few months ago, I switched from the StripeEvent::retrieve($id) approach on 2 larges projects (not with Cashier, my own implementation) to the Signature approach, that I feel more comfortable, and more robust.

It's easy to implement with a recent version of the Stripe library, and it can be done this way :

  • Keep the old way available
  • Check if new way is enabled, then use it. If not, use the old way.

This require 2 new env variables.

What do you think ?

enhancement

Most helpful comment

Thanks @mcordingley,

The $request->header() method did not work for me, I had to use $request->server() instead.
Here is my working set up in case it helps someone.

// App\Http\Middleware\VerifyStripeWebhookSignature

public function handle($request, Closure $next)
{
    if (! config('services.stripe.webhook.secret')) {
        return $next($request);
    }

    try {
        WebhookSignature::verifyHeader(
            $request->getContent(),
            $request->server('HTTP_STRIPE_SIGNATURE'),
            config('services.stripe.webhook.secret'),
            config('services.stripe.webhook.tolerance')
        );
    } catch (SignatureVerification $e) {
        return abort(404);
    }

    return $next($request);
}
// config/services.php

'stripe' => [
    'model' => App\User::class,
    'key' => env('STRIPE_KEY'),
    'secret' => env('STRIPE_SECRET'),
    'webhook' => [
        'secret' => env('STRIPE_WEBHOOK_SECRET'),
        'tolerance' => env('STRIPE_WEBHOOK_TOLERANCE', 300),
    ],
],
// App\Http\Kernel

protected $routeMiddleware = [
    // ...
    'from.stripe' => \App\Http\Middleware\VerifyStripeWebhookSignature::class,
];

All 6 comments

Without the signature verification, there really is no way to prevent malicious users from spoofing updates from Stripe. Depending on the application and the message spoofed, this could be bad.

Implementing this should be as simple as:

if (config('services.stripe.hook.secret')) {
    // throws Stripe\Error\SignatureVerification exception on verification failure
    Stripe\WebhookSignature::verifyHeader(
        $request->getContent(),
        $request->header('HTTP_STRIPE_SIGNATURE'),
        config('services.stripe.hook.secret'),
        config('services.stripe.hook.tolerance') // default in config file to 300 to match upstream
    );
}

This feels like something that would make good middleware:

class WebhookController extends Controller
{
    public function __construct()
    {
        $this->middleware('stripe.webhook');
    }
}

Thanks @mcordingley,

The $request->header() method did not work for me, I had to use $request->server() instead.
Here is my working set up in case it helps someone.

// App\Http\Middleware\VerifyStripeWebhookSignature

public function handle($request, Closure $next)
{
    if (! config('services.stripe.webhook.secret')) {
        return $next($request);
    }

    try {
        WebhookSignature::verifyHeader(
            $request->getContent(),
            $request->server('HTTP_STRIPE_SIGNATURE'),
            config('services.stripe.webhook.secret'),
            config('services.stripe.webhook.tolerance')
        );
    } catch (SignatureVerification $e) {
        return abort(404);
    }

    return $next($request);
}
// config/services.php

'stripe' => [
    'model' => App\User::class,
    'key' => env('STRIPE_KEY'),
    'secret' => env('STRIPE_SECRET'),
    'webhook' => [
        'secret' => env('STRIPE_WEBHOOK_SECRET'),
        'tolerance' => env('STRIPE_WEBHOOK_TOLERANCE', 300),
    ],
],
// App\Http\Kernel

protected $routeMiddleware = [
    // ...
    'from.stripe' => \App\Http\Middleware\VerifyStripeWebhookSignature::class,
];

This looks like a valid and good addition 馃憤

Welcoming PRs for this!

I'll put one together later this week, once I have the time.

Thanks @mcordingley for adding this!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tania-pets picture tania-pets  路  3Comments

signvikas picture signvikas  路  5Comments

4UForever picture 4UForever  路  3Comments

josephcocoa picture josephcocoa  路  3Comments

peeyush1234 picture peeyush1234  路  4Comments