Verifying Shopify Webhooks for Public apps

Solved
garyrgilbert
Explorer
55 6 13

Hi Greg,

WTF they aren't checking the body contents of the webhook but only the URL params minus the hmac and/or signature param? I will give it a try tomorrow, if it works the documentation is total BS.

 

thanks for the reply!

- Was my reply helpful? Click Like to let me know!
- Was your question answered? Mark it as an Accepted Solution
0 Likes
Greg_Kujawa
Shopify Partner
1016 83 243

Yes, please keep us posted. As I said, the app proxy call is what provides the HMAC validation elements in the actual URL as query parameters. The webhook provides the HMAC value in the header, which you have to factor the actual request body in order to compute things. Different ways to handle, but the last code I posted demonstrates how to work with either scenario, 

0 Likes
dustinvn
New Member
2 0 0

Could you have to solve my same issue on NodeJS ?

I tried to use shared key or signed webhook key but hmac and verified mac are never not matched.

 

Please help. This is my code:

// MY CODE
const hmac = req.header('X-Shopify-Hmac-Sha256')
const data = JSON.stringify(req.body)
console.log(data)
let secretKey = SHOPIFY_SECRET_KEY
// secretKey = SIGNED_WEBHOOK_KEY
const verifiedHmac = crypto
.createHmac('sha256', secretKey)
.update(data)
.digest('base64')

console.log(verifiedHmac)
console.log(hmac)

// HMAC OUPUT:
yrFvoapW6QJpRRPinNshHRKIj2zUvJYrFpJEIZkKXEg=
slF4b174ph+yRruzJAw/KiqaA7SUYOM8iL93FjFKB6g=

// DATA:

{"id":820982911946154500,"email":"jon@doe.ca","closed_at":null,"created_at":"2021-07-17T16:45:52+07:00","updated_at":"2021-07-17T16:45:52+07:00","number":234,"note":null,"token":"123456abcd","gateway":null,"test":true,"total_price":"255","subtotal_price":"245","total_weight":0,"total_tax":"0","taxes_included":false,"currency":"VND","financial_status":"voided","confirmed":false,"total_discounts":"5","total_line_items_price":"250","cart_token":null,"buyer_accepts_marketing":true,"name":"#9999","referring_site":null,"landing_site":null,"cancelled_at":"2021-07-17T16:45:52+07:00","cancel_reason":"customer","total_price_usd":null,"checkout_token":null,"reference":null,"user_id":null,"location_id":null,"source_identifier":null,"source_url":null,"processed_at":null,"device_id":null,"phone":null,"customer_locale":"en","app_id":null,"browser_ip":null,"landing_site_ref":null,"order_number":1234,"discount_applications":[{"type":"manual","value":"5.0","value_type":"fixed_amount","allocation_method":"each","target_selection":"explicit","target_type":"line_item","description":"Discount","title":"Discount"}],"discount_codes":[],"note_attributes":[],"payment_gateway_names":["visa","bogus"],"processing_method":"","checkout_id":null,"source_name":"web","fulfillment_status":"pending","tax_lines":[],"tags":"","contact_email":"jon@doe.ca","order_status_url":"https://ducstore3.myshopify.com/55670145208/orders/123456abcd/authenticate?key=abcdefg","presentment_currency":"VND","total_line_items_price_set":{"shop_money":{"amount":"250","currency_code":"VND"},"presentment_money":{"amount":"250","currency_code":"VND"}},"total_discounts_set":{"shop_money":{"amount":"5","currency_code":"VND"},"presentment_money":{"amount":"5","currency_code":"VND"}},"total_shipping_price_set":{"shop_money":{"amount":"10","currency_code":"VND"},"presentment_money":{"amount":"10","currency_code":"VND"}},"subtotal_price_set":{"shop_money":{"amount":"245","currency_code":"VND"},"presentment_money":{"amount":"245","currency_code":"VND"}},"total_price_set":{"shop_money":{"amount":"255","currency_code":"VND"},"presentment_money":{"amount":"255","currency_code":"VND"}},"total_tax_set":{"shop_money":{"amount":"0","currency_code":"VND"},"presentment_money":{"amount":"0","currency_code":"VND"}},"line_items":[{"id":487817672276298560,"variant_id":null,"title":"Aviator sunglasses","quantity":1,"sku":"SKU2006-001","variant_title":null,"vendor":null,"fulfillment_service":"manual","product_id":788032119674292900,"requires_shipping":true,"taxable":true,"gift_card":false,"name":"Aviator sunglasses","variant_inventory_management":null,"properties":[],"product_exists":true,"fulfillable_quantity":1,"grams":100,"price":"90","total_discount":"0","fulfillment_status":null,"price_set":{"shop_money":{"amount":"90","currency_code":"VND"},"presentment_money":{"amount":"90","currency_code":"VND"}},"total_discount_set":{"shop_money":{"amount":"0","currency_code":"VND"},"presentment_money":{"amount":"0","currency_code":"VND"}},"discount_allocations":[],"duties":[],"admin_graphql_api_id":"gid://shopify/LineItem/487817672276298554","tax_lines":[]},{"id":976318377106520300,"variant_id":null,"title":"Mid-century lounger","quantity":1,"sku":"SKU2006-020","variant_title":null,"vendor":null,"fulfillment_service":"manual","product_id":788032119674292900,"requires_shipping":true,"taxable":true,"gift_card":false,"name":"Mid-century lounger","variant_inventory_management":null,"properties":[],"product_exists":true,"fulfillable_quantity":1,"grams":1000,"price":"160","total_discount":"5","fulfillment_status":null,"price_set":{"shop_money":{"amount":"160","currency_code":"VND"},"presentment_money":{"amount":"160","currency_code":"VND"}},"total_discount_set":{"shop_money":{"amount":"5","currency_code":"VND"},"presentment_money":{"amount":"5","currency_code":"VND"}},"discount_allocations":[{"amount":"5","discount_application_index":0,"amount_set":{"shop_money":{"amount":"5","currency_code":"VND"},"presentment_money":{"amount":"5","currency_code":"VND"}}}],"duties":[],"admin_graphql_api_id":"gid://shopify/LineItem/976318377106520349","tax_lines":[]}],"fulfillments":[],"refunds":[],"total_tip_received":"0.0","original_total_duties_set":null,"current_total_duties_set":null,"admin_graphql_api_id":"gid://shopify/Order/820982911946154508","shipping_lines":[{"id":271878346596884000,"title":"Generic Shipping","price":"10","code":null,"source":"shopify","phone":null,"requested_fulfillment_service_id":null,"delivery_category":null,"carrier_identifier":null,"discounted_price":"10","price_set":{"shop_money":{"amount":"10","currency_code":"VND"},"presentment_money":{"amount":"10","currency_code":"VND"}},"discounted_price_set":{"shop_money":{"amount":"10","currency_code":"VND"},"presentment_money":{"amount":"10","currency_code":"VND"}},"discount_allocations":[],"tax_lines":[]}],"billing_address":{"first_name":"Bob","address1":"123 Billing Street","phone":"555-555-BILL","city":"Billtown","zip":"K2P0B0","province":"Kentucky","country":"United States","last_name":"Biller","address2":null,"company":"My Company","latitude":null,"longitude":null,"name":"Bob Biller","country_code":"US","province_code":"KY"},"shipping_address":{"first_name":"Steve","address1":"123 Shipping Street","phone":"555-555-SHIP","city":"Shippington","zip":"40003","province":"Kentucky","country":"United States","last_name":"Shipper","address2":null,"company":"Shipping Company","latitude":null,"longitude":null,"name":"Steve Shipper","country_code":"US","province_code":"KY"},"customer":{"id":115310627314723950,"email":"john@test.com","accepts_marketing":false,"created_at":null,"updated_at":null,"first_name":"John","last_name":"Smith","orders_count":0,"state":"disabled","total_spent":"0.00","last_order_id":null,"note":null,"verified_email":true,"multipass_identifier":null,"tax_exempt":false,"phone":null,"tags":"","last_order_name":null,"currency":"VND","accepts_marketing_updated_at":null,"marketing_opt_in_level":null,"admin_graphql_api_id":"gid://shopify/Customer/115310627314723954","default_address":{"id":715243470612851200,"customer_id":115310627314723950,"first_name":null,"last_name":null,"company":null,"address1":"123 Elm St.","address2":null,"city":"Ottawa","province":"Ontario","country":"Canada","zip":"K2H7A8","phone":"123-123-1234","name":"","province_code":"ON","country_code":"CA","country_name":"Canada","default":true}}}


 

0 Likes
limeforadime
New Member
1 0 0

For your "data" variable there, you're going to need to get the "raw body" instead. Getting this will vary by whichever body parsing package you're using, but this should point you in the right direction.

 

0 Likes
garyrgilbert
Explorer
55 6 13

Ive tried every manner and I can't get it to work with webhooks created through the API.. verifying webhooks created for private apps works without issue using the example methods provided on https://shopify.dev/apps/webhooks.

e.g. in java using the described method

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class Main {

public static void main(String[] args) {
    try {
            String secret = "shpss_xxx";
            String message = "raw_json_body";

            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
            sha256_HMAC.init(secret_key);

            byte[] hash = sha256_HMAC.doFinal(message.getBytes());
            System.out.println(Base64.getEncoder().encodeToString(hash));
        }
        catch (Exception e){
            System.out.println("Error");
        }
}
}

 

This never returns the correct hash.. I would really finally like to have someone from shopify or another developer show me a working example for validating webhooks created via the API for public apps.

 

Cheers and thanks

- Was my reply helpful? Click Like to let me know!
- Was your question answered? Mark it as an Accepted Solution
0 Likes