How to decode WooCommerce Webhook Secret?
Asked Answered
M

6

11

I can't find any information on what algorithm to use to decode WooCommerce webhook field X-Wc-Webhook-Signature in PHP. Does anyone know how to decode it?

Thanks!

Merrymaker answered 1/6, 2017 at 16:15 Comment(2)
Hi and welcome to SO. Please read stackoverflow.com/help/how-to-ask on how to ask a proper question. For now: can you add some code you already tried? Were happy to help you debugging, but if youre in search of someone to write your code this not the right place.Kalliekallista
Hi Fabian. Sorry for the late reply. I'm not looking for someone to write the code. I am just searching for how WooCommerce is hashing the secret so that I can find the correct algorithm to match the hash on my server. I have not written any code yet, as I do not know what algorithm they use. Thanks!Merrymaker
S
8

Expanding on the current answers, this is the PHP code snippet you need:

$sig = base64_encode(hash_hmac('sha256', $request_body, $secret, true));

Where $secret is your secret, $request_body is the request body, which can be fetched with file_get_contents('php://input'); The $sig value should then be equal to the X-Wc-Webhook-Signature request header.

Skive answered 3/10, 2017 at 2:49 Comment(1)
I am trying your code but for some reason I can never get the values to match? Could I be missing anything else?Headband
E
8

To expand on the laravel solution this is how I created middleware to validate the incoming webhook.

Create middleware. The application that I am using keeps the WooCommerce consumer key and secret in a table assigned to a given store.

class ValidateWebhook
{
    /**
     * Validate that the incoming request has been signed by the correct consumer key for the supplied store id
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $signature = $request->header('X-WC-Webhook-Signature');
        if (empty($signature)) {
            return response(['Invalid key'], 401);
        }

        $store_id = $request['store'];
        $consumer_key = ConsumerKeys::fetchConsumerSecretByStoreId($store_id);

        $payload = $request->getContent();
        $calculated_hmac = base64_encode(hash_hmac('sha256', $payload, $consumer_key, true));

        if ($signature != $calculated_hmac) {
            return response(['Invalid key'], 401);
        }

        return $next($request);
    }
}

Register the middleware in Kernel.php

        'webhook' => \App\Http\Middleware\ValidateWebhook::class,

Protect the webhook route with the middleware

Route::post('webhook', 'PrintController@webhook')->middleware('webhook');
Enhance answered 25/2, 2021 at 10:3 Comment(0)
D
3

Here is my solution for this question. You need to generate a sha256 hash for the content that was sent [payload] and encode it in base64. Then just compare the generated hash with the received one.

$secret = 'your-secret-here';
$payload = file_get_contents('php://input');
$receivedHeaders = apache_request_headers();
$receivedHash = $receivedHeaders['X-WC-Webhook-Signature'];
$generatedHash = base64_encode(hash_hmac('sha256', $payload, $secret, true));
if($receivedHash === $generatedHash):
        //Verified, continue your code
else:
        //exit
endif;
Dogwatch answered 8/5, 2020 at 14:28 Comment(0)
P
1

According to the docs: "A base64 encoded HMAC-SHA256 hash of the payload"

I'm guessing that the payload in this instances is the secret you've supplied in the webhook properties.

Source: https://woocommerce.github.io/woocommerce-rest-api-docs/v3.html#webhooks-properties

EDIT

Further digging indicates that the payload is the body of the request. So you'd use a HMAC library to create a sha256 hash of the payload using your webhook secret, and then base64 encode the result, and do the comparison with X-Wc-Webhook-Signature and see if they match.

You can generate a key pair from the following URL:

/wc-auth/v1/authorize?app_name=my_app&scope=read_write&user_id=<a_unique_id_that_survives_the_roundtrip>&return_url=<where_to_go_when_the_flow_completes>&callback_url=<your_server_url_that_will_receieve_the_following_params>

These are the params the callback URL receieves in the request body:

{
  consumer_key: string,
  consumer_secret: string,
  key_permissions: string,
  user_id: string,
}
Paediatrics answered 2/8, 2017 at 12:7 Comment(1)
How do you generate the secret? Is the only way to create one through the UI?Allomorph
P
0

Here's my laravel / lumen function to verify the webhook request, hope it helps someone!

private function verifyWebhook(Request $request) {
    $signature = $request->header('x-wc-webhook-signature');

    $payload = $request->getContent();
    $calculated_hmac = base64_encode(hash_hmac('sha256', $payload, 'WOOCOMMERCE_KEY', true));

    if($signature != $calculated_hmac) {
      abort(403);
    }

    return true;
}
Porringer answered 21/5, 2020 at 2:32 Comment(0)
L
0

If all the answers will fail: Your secret (defined in woocommerce) should only contain letters and numbers!

Longeron answered 11/5, 2023 at 14:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.