Repository navigation
Expand file tree
/
Copy pathwebhook_server.py
More file actions
50 lines (41 loc) · 1.76 KB
/
Copy pathwebhook_server.py
File metadata and controls
50 lines (41 loc) · 1.76 KB
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
41
42
43
44
45
46
47
48
49
50
"""Minimal webhook receiver that verifies X-Salvagealert-Signature.
Usage: SALVAGEALERT_WEBHOOK_SECRET=... python webhook_server.py (listens on :8787)
"""
import hashlib
import hmac
import json
import os
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
SECRET = os.environ["SALVAGEALERT_WEBHOOK_SECRET"]
seen = set() # use a database in production, keyed on the delivery id
def verify(raw: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
if not header:
return False
parts = dict(p.strip().split("=", 1) for p in header.split(","))
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
raw = self.rfile.read(int(self.headers.get("Content-Length", 0))) # RAW body
if not verify(raw, self.headers.get("X-Salvagealert-Signature"), SECRET):
self.send_response(401); self.end_headers(); return
self.send_response(200); self.end_headers(); self.wfile.write(b"ok")
delivery_id = self.headers.get("X-Salvagealert-Delivery-Id")
if delivery_id in seen: # deliveries can repeat
return
seen.add(delivery_id)
body = json.loads(raw)
data = body["data"]
if data["kind"] == "watch_match":
lot = data["lot"]
print(f"[{body['event']}] {lot['year']} {lot['make']} {lot['model']} {lot.get('detailUrl')}")
else:
print(f"[{body['event']}] {data['kind']}")
HTTPServer(("", 8787), Handler).serve_forever()