from __future__ import annotations from pathlib import Path from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import json import os import sys ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) CAPTURE_FILE = ROOT / "tmp_webhook_capture.jsonl" class CaptureHandler(BaseHTTPRequestHandler): def do_POST(self) -> None: try: length = int(self.headers.get("Content-Length", "0")) except ValueError: length = 0 raw_body = self.rfile.read(length) if length else b"" record = { "path": self.path, "headers": {key: value for key, value in self.headers.items()}, "body": _decode_json(raw_body), } with CAPTURE_FILE.open("a", encoding="utf-8") as handle: handle.write(json.dumps(record, ensure_ascii=False) + "\n") payload = json.dumps({"ok": True, "captured": True}).encode("utf-8") self.send_response(200) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(payload))) self.send_header("x-request-id", "local-capture") self.end_headers() self.wfile.write(payload) def log_message(self, format: str, *args) -> None: return def main(env=None) -> int: env = os.environ if env is None else env host = env.get("WEBHOOK_CAPTURE_HOST", "127.0.0.1") port = int(env.get("WEBHOOK_CAPTURE_PORT", "8787")) server = ThreadingHTTPServer((host, port), CaptureHandler) print(f"Webhook capture listening on http://{host}:{port}") server.serve_forever() return 0 def _decode_json(raw_body: bytes): if not raw_body: return {} try: return json.loads(raw_body.decode("utf-8")) except Exception: return {"raw": raw_body.decode("utf-8", errors="replace")} if __name__ == "__main__": raise SystemExit(main())