I’ve been using this post as a guide, but I’m still running into a mis-match when trying to validate the X-Ghost-Signature.
Here’s my code thus far:
def post(self, request, *args, **kwargs):
ghost_webhook_secret = SiteConfiguration.get_solo().ghost_webhook_secret
if ghost_webhook_secret is None or ghost_webhook_secret == "":
logger.error("No ghost_webhook_secret configured")
return HttpResponse(status=401)
ghost_signature = request.headers.get("X_GHOST_SIGNATURE")
if ghost_signature is None:
logger.error("No X_GHOST_SIGNATURE header")
return HttpResponse(status=401)
# Of form sha256=c992ed22cb8a7eae6b925acf97c707be55a15c65155b1f8c0b8749549620f044, t=1724537500613
try:
parts = ghost_signature.split(", ")
signature = parts[0].split("=")[1]
timestamp = parts[1].split("=")[1]
except ValueError:
logger.error("Invalid X_GHOST_SIGNATURE header, expected two")
return HttpResponse(status=401)
body = request.body
# Make sure body is bytes
if not isinstance(body, bytes):
body = body.encode('utf-8')
# Make sure the key is bytes
if not isinstance(ghost_webhook_secret, bytes):
ghost_webhook_secret = ghost_webhook_secret.encode('utf-8')
# Calculate the HMAC
calculated_signature = hmac.new(ghost_webhook_secret, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(calculated_signature, signature):
logger.error("Signatures do not match - webhook not processed")
return HttpResponse(status=401)
payload = request.data
print("Headers: {}".format(request.headers))
print(f"Payload: {payload}")
return HttpResponse(status=200)
I’ve been staring at this for a while now, so figured it’s time to post and see if others with less glazed eyes can shed any light!
btw: if you’re curious, this is what it’s like from the debugger (the shared key is correct, and now, also not what’s in that pic :):
# Make sure timestamp is bytes
if not isinstance(timestamp, bytes):
timestamp = timestamp.encode('utf-8')
# Make sure the key is bytes
if not isinstance(ghost_webhook_secret, bytes):
ghost_webhook_secret = ghost_webhook_secret.encode('utf-8')
# Calculate the HMAC (not documented from what I can tell, but timestamp has to be included)
sign_this_please = body + timestamp
calculated_signature = hmac.new(ghost_webhook_secret, sign_this_please, hashlib.sha256).hexdigest()
i,e: include timestamp after the body (I was using just body, previously).
Um.
Now it passes.
(btw: Im using ghost 5.89.1 - which might be relevant)