#!/usr/bin/env python3 """ Minimal webhook receiver for Finzep - Python 3.8+ (pip install cryptography). Save as webhook_receiver.py next to fzp_client.py. Specification: payout-api-guide.html (section "Webhooks") PORT=8080 CLIENT_PRIVATE_KEY=client-private.pem SERVER_PUBLIC_KEY=server-public.pem python webhook_receiver.py Verifies OUR signature, decrypts the event with YOUR private key, ignores an event it has already handled (events can arrive more than once) and answers HTTP 200 quickly. Do the real work in handle(). Put it behind https in production. """ import json import os from http.server import BaseHTTPRequestHandler, HTTPServer from fzp_client import open_webhook CLIENT_PRIVATE = open(os.environ.get("CLIENT_PRIVATE_KEY", "client-private.pem"), encoding="utf-8").read() SERVER_PUBLIC = open(os.environ.get("SERVER_PUBLIC_KEY", "server-public.pem"), encoding="utf-8").read() handled = set() # use your database in production: eventId is unique def handle(event): # event["event"]: payout.success | payout.failed | refund.accepted | refund.rejected | webhook.test print("webhook", event["event"], event.get("transactionId"), event["statusName"], json.dumps(event), flush=True) class Hook(BaseHTTPRequestHandler): def do_POST(self): body = self.rfile.read(int(self.headers.get("Content-Length", 0))).decode("utf-8") try: event = json.loads(open_webhook(body, self.headers.get("X-Webhook-Id"), CLIENT_PRIVATE, SERVER_PUBLIC)) if event["eventId"] not in handled: handled.add(event["eventId"]) handle(event) self.send_response(200) # any 2xx tells us it arrived self.end_headers() self.wfile.write(b"ok") except Exception as e: print("rejected webhook:", e, flush=True) self.send_response(400) # we will retry later self.end_headers() self.wfile.write(b"invalid") def log_message(self, *args): pass if __name__ == "__main__": port = int(os.environ.get("PORT", "8080")) print("listening on", port, flush=True) HTTPServer(("0.0.0.0", port), Hook).serve_forever()