Woocommerce – Verificando a assinatura no Webhook
Nos últimos tempos, tenho trabalhado bastante com PHP e em alguns projetos escolhi usar o WordPress, que já uso aqui no blog, estou habituado e é relativamente fácil conseguir material sobre ele na internet.
Desses projetos, um deles é uma loja virtual, e comecei a conhecer e utilizar o famoso plugin do WooCommerce, que transforma seu WordPress em uma loja virtual totalmente funcional.
Nesse projeto, surgiu a necessidade de quando ocorrer determinados eventos na loja, como a criação do pedido, comunicar o evento para um outro sistema. Foi aí que descobri a existência dos Webhooks do WooCommerce.
O que é Webhook
Segundo a Wikipedia, Webhooks são callbacks HTTP definidos pelo utilizador, e isso realmente resume muito bem.
Quem está acostumado já em trabalhar com eventos no Javascript por exemplo, entenderá fácil. No WooCommerce, algumas ações como criar ou atualizar um produto e criar um pedido, geram eventos. Nesses eventos, o motor do WooCommerce pode chamar páginas na Web passando algum conteúdo, semelhante no jQuery que quando algo acontece em determinado seletor você chama uma função definida por você. Assim, basta criar uma página que receberá este conteúdo e processar de alguma maneira.
Porém, como toda página, ela é aberta publicamente, e alguém com a URL pode tentar enviar dados falsos para ele, por exemplo para dar um desconto de 99% no próprio pedido. Para evitar isso, um dos cabeçalhos enviados para a página contém uma assinatura, que deve ser verificada para confirmar a identidade de quem enviou aquele conteúdo. E é para isso que estamos aqui!
Na rede, não consegui encontrar nenhum exemplo prático em PHP, além da documentação atual nessa parte não ajudar muito, e tive um pouco de dificuldade em entender como isso poderia ser feito, então vou compartilhar com vocês o meu método, que é extremamente simples e fácil.
Verificando o X-WC-Webhook-Signature
O cabeçalho que contém a assinatura se chama X-WC-Webhook-Signature. Para calcularmos o nosso valor para comparar com ele, vamos precisar dos dados do conteúdo e do valor do campo “Segredo” quando cadastramos o Webhook, em Woocommerce > Configurações > API dentro do painel do WordPress.
Com esse valor e o conteúdo, precisamos utilizar a função do PHP hash_hmac e codificar o retorno para base64. Para isso, criei esta função abaixo que faz tudo isso já:
1 2 3 4 |
// função para checar se a assinatura bate, retorna true se bate, false se não. function checkSignature($webhook_signature, $webhook_key, $content) { return strcmp($webhook_signature, base64_encode(hash_hmac('sha256', $content, $webhook_key, true))) === 0; } |
Bem simples e direta, apenas uma linha pra fazer tudo que precisamos. Com isso, já sabemos se podemos confiar no conteúdo enviado ou não.
Abaixo, um exemplo bem simples de script, que apenas escreve as informações recebidas e a checagem de assinatura em arquivo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<?php // dados enviados pelo woocommerce $input = file_get_contents('php://input'); // conversão da string em formato json para array associativo $inputJson = json_decode($input,true); // assinatura no header $allHeaders = getallheaders(); $signature = $allHeaders['X-WC-Webhook-Signature']; // chave secreta, também escolhida no cadastro do webhook do woocommerce define ('SHARED_SECRET', 'teste edu'); // arquivo para escrever saída de teste $fp = fopen('out.teste.txt', 'w'); if ($fp !== false) { // todos os headers $str = 'headers: ' . var_export($allHeaders, true) . PHP_EOL; // assinatura $str .= 'X-WC-Webhook-Signature: ' . $allHeaders['X-WC-Webhook-Signature'] . PHP_EOL; // gera assinatura usando o conteúdo e a chave secreta $hashHmac = base64_encode(hash_hmac('sha256', $input, SHARED_SECRET, true)); $str .= 'hashHmac: ' . $hashHmac . PHP_EOL; // checa assinatura usando a função definida ali embaixo $check = checkSignature($allHeaders['X-WC-Webhook-Signature'], SHARED_SECRET, $input); // resultado da checagem $str .= 'assinatura ok? ' . var_export($check, true) . PHP_EOL; // conteúdo no formato de array associativo, por exemplo $inputJson['product']['title'] $str .= 'json: ' . var_export($inputJson, true) . PHP_EOL; // escreve o texto no arquivo fwrite($fp, $str); // fecha arquivo fclose($fp); } // função para checar se a assinatura bate, retorna true se bate, false se não. function checkSignature($webhook_signature, $webhook_key, $content) { $signed_data = $content; $r = strcmp($webhook_signature, base64_encode(hash_hmac('sha256', $signed_data, $webhook_key, true))); return $r === 0; } |