Verificar assinaturas
Confirme que cada pedido de webhook vem realmente do Pulsar e não foi alterado pelo caminho.
O seu endpoint é um URL público, por isso qualquer pessoa lhe pode enviar um pedido. Cada notificação do Pulsar inclui um cabeçalho X-Pulsar-Signature, um hash do corpo do pedido calculado com o seu segredo de assinatura. O segredo só é conhecido pelo Pulsar e por si, por isso uma assinatura que coincide prova que o pedido veio do Pulsar e que o corpo não foi alterado.
A assinatura é o HMAC-SHA256 do corpo em bruto do pedido, com o seu segredo de assinatura como chave, escrito em hexadecimal em minúsculas.
Use o segredo exatamente como o Pulsar lho fornece
O segredo de assinatura é uma cadeia de 32 caracteres. Use o seu texto (bytes UTF-8) como chave do HMAC, tal como está. Não o descodifique de Base64 nem de hexadecimal. A descodificação costuma funcionar sem erros, mas produz a chave errada, e então todas as verificações de assinatura falham.
Como verificar um pedido
Leia o corpo em bruto
Calcule a assinatura sobre os bytes exatos que o Pulsar enviou, antes de qualquer parsing do JSON. Fazer o parsing e voltar a serializar o JSON pode alterar os espaços ou a ordem das chaves, o que altera o hash. A maioria das frameworks fornece o corpo em bruto separadamente; consulte os exemplos abaixo.
Calcule a assinatura esperada
Calcule o HMAC-SHA256 do corpo em bruto com o seu segredo de assinatura como chave, e codifique o resultado em hexadecimal.
Compare-a com o cabeçalho
Compare o seu resultado com o cabeçalho X-Pulsar-Signature através de uma comparação de tempo constante (hmac.compare_digest em Python, crypto.timingSafeEqual em Node.js). Um == simples pode revelar, pelo tempo de resposta, que parte de uma assinatura adivinhada estava certa.
Aceite ou rejeite
Se as assinaturas coincidirem, processe o evento e devolva 2XX. Se o cabeçalho faltar ou não coincidir, devolva 401 e ignore o pedido.
Teste primeiro a sua verificação com um ping
O Pulsar não volta a tentar após uma resposta 4XX (consulte Novas tentativas). Por isso, se o seu segredo estiver mal configurado, os eventos reais são rejeitados e ficam por entregar até o Pulsar os reenviar. Peça um ping e confirme que o seu endpoint devolve 2XX antes de depender dele.
Código de exemplo
Ambos os exemplos leem o segredo de uma variável de ambiente PULSAR_WEBHOOK_SECRET.
import hashlib
import hmac
import os
from flask import Flask, request
app = Flask(__name__)
SECRET = os.environ["PULSAR_WEBHOOK_SECRET"].encode("utf-8")
@app.post("/webhooks/pulsar")
def pulsar_webhook():
signature = request.headers.get("X-Pulsar-Signature", "")
# Os bytes exatos que o Pulsar enviou, antes de fazer o parsing do JSON.
body = request.get_data()
expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
return "", 401
notification = request.get_json()
if notification["eventType"] == "Ping":
return "", 200
# Coloque o trabalho em fila e responda de imediato; consulte "Responda com um 2XX, rapidamente".
enqueue(notification)
return "", 202Pedidos repetidos
A assinatura cobre apenas o corpo, por isso qualquer pessoa que obtenha uma cópia de um pedido, por exemplo de um log, poderia voltar a enviá-lo sem alterações e continuaria a passar a verificação. Proteja-se da mesma forma que trata os duplicados: registe cada eventId que processar e ignore os que já tiver visto.
Não rejeite pedidos só porque eventTimestamp é antigo. As novas tentativas e os reenvios voltam a enviar propositadamente o evento original, com o seu carimbo de data/hora original, possivelmente muito mais tarde.