Skip to content

Commit 78c35cf

Browse files
committed
chore: add webhook verification to python quickstart
1 parent 732d0c6 commit 78c35cf

File tree

1 file changed

+40
-11
lines changed

1 file changed

+40
-11
lines changed

python/inbound/app.py

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,56 @@
1+
import os
12
from datetime import datetime
23
import json
34
import logging
5+
import hashlib
6+
import hmac
7+
import base64
48
from flask import Flask, request
59

6-
logging.basicConfig(
7-
level=logging.INFO,
8-
handlers=[logging.StreamHandler()]
9-
)
10+
logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler()])
1011

1112
app = Flask(__name__)
1213
app.logger.setLevel(logging.INFO)
1314

15+
HOOKDECK_WEBHOOK_SECRET = os.getenv("HOOKDECK_WEBHOOK_SECRET")
16+
17+
18+
def verify_webhook(request):
19+
if HOOKDECK_WEBHOOK_SECRET is None:
20+
app.logger.warn(
21+
"No HOOKDECK_WEBHOOK_SECRET found in environment variables. Skipping verification."
22+
)
23+
return False
24+
25+
# Extract x-hookdeck-signature and x-hookdeck-signature-2 headers from the request
26+
hmac_header = request.headers.get("x-hookdeck-signature")
27+
hmac_header2 = request.headers.get("x-hookdeck-signature-2")
28+
29+
# Create a hash based on the raw body
30+
hash = base64.b64encode(
31+
hmac.new(
32+
HOOKDECK_WEBHOOK_SECRET.encode(), request.data, hashlib.sha256
33+
).digest()
34+
).decode()
35+
36+
# Compare the created hash with the value of the x-hookdeck-signature
37+
# Also check x-hookdeck-signature-2 header in case the secret was rolled
38+
return hash == hmac_header or (hmac_header2 and hash == hmac_header2)
39+
1440

1541
@app.route("/<path:path>", methods=["POST"])
1642
def handle(path):
17-
app.logger.info("webhook_received %s %s",
18-
datetime.now().isoformat(),
19-
json.dumps(request.json, indent=2))
43+
app.logger.info(
44+
"webhook_received %s %s",
45+
datetime.now().isoformat(),
46+
json.dumps(request.json, indent=2),
47+
)
2048

21-
return {
22-
"status": "ACCEPTED"
23-
}
49+
if not verify_webhook(request):
50+
return {"status": "UNAUTHORIZED"}, 403
51+
else:
52+
return {"status": "ACCEPTED"}, 200
2453

2554

2655
if __name__ == "__main__":
27-
app.run(debug=True, port=3030)
56+
app.run(debug=True, port=3031)

0 commit comments

Comments
 (0)