-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwebhook_handler.py
More file actions
81 lines (62 loc) · 2.74 KB
/
Copy pathwebhook_handler.py
File metadata and controls
81 lines (62 loc) · 2.74 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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
"""Webhook Handler Example (Flask)
Verify and process incoming webhook events from getpeppr.
Uses HMAC-SHA256 signature verification.
"""
import hashlib
import hmac
import json
import os
import time
from flask import Flask, Response, request
app = Flask(__name__)
WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"]
def verify_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
"""Verify the Getpeppr-Signature header using HMAC-SHA256."""
if not signature_header:
return False
parts = dict(pair.split("=", 1) for pair in signature_header.split(","))
timestamp = parts.get("t", "")
received_sig = parts.get("s", "")
# Reject signatures older than 5 minutes
try:
if abs(time.time() - int(timestamp)) > 300:
return False
except ValueError:
return False
payload = f"{timestamp}.{raw_body.decode('utf-8')}"
expected_sig = hmac.new(
secret.encode("utf-8"),
payload.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected_sig, received_sig)
@app.route("/webhooks/getpeppr", methods=["POST"])
def handle_webhook() -> Response:
raw_body = request.get_data()
signature = request.headers.get("Getpeppr-Signature", "")
if not verify_signature(raw_body, signature, WEBHOOK_SECRET):
return Response("Invalid signature", status=400)
event = json.loads(raw_body)
match event["type"]:
case "invoice.sent":
print(f"Invoice {event['data']['invoiceId']} was delivered to the recipient's access point")
case "invoice.accepted":
print(f"Invoice {event['data']['invoiceId']} was accepted by the recipient")
case "invoice.refused":
print(f"Invoice {event['data']['invoiceId']} was refused by the recipient")
case "invoice.error":
print(f"Invoice {event['data']['invoiceId']} delivery failed")
case "invoice.paid":
print(f"Invoice {event['data']['invoiceId']} was paid")
# Inbound reception (pilot — contact support to enable): a supplier sent
# a document TO one of your Legal Entities. Delivery is at-least-once —
# deduplicate on data.receivedDocumentId, the stable idempotency key.
case "inbound.invoice.received":
print(f"Received an invoice ({event['data']['receivedDocumentId']}) from the Peppol network")
case "inbound.creditnote.received":
print(f"Received a credit note ({event['data']['receivedDocumentId']}) from the Peppol network")
case _:
print(f"Unhandled event type: {event['type']}")
return Response(json.dumps({"received": True}), status=200, content_type="application/json")
if __name__ == "__main__":
app.run(port=3000)