Fix BTCPay webhook events and delivery

This commit is contained in:
xmrcheckout
2026-01-08 17:50:09 +00:00
parent 7bde17a3eb
commit af60d1d4c6
3 changed files with 51 additions and 1 deletions
+3
View File
@@ -37,7 +37,9 @@ router = APIRouter()
BTCPAY_PAYMENT_METHOD = "XMR-CHAIN"
BTCPAY_PAYMENT_METHOD_ALIASES = {BTCPAY_PAYMENT_METHOD, "XMR", "XMR_CHAIN"}
BTCPAY_WEBHOOK_EVENTS = {
"InvoiceCreated",
"InvoiceReceivedPayment",
"InvoicePaidInFull",
"InvoicePaymentSettled",
"InvoiceProcessing",
"InvoiceExpired",
@@ -323,6 +325,7 @@ def create_invoice(
except HTTPException:
pass
dispatch_webhooks(db, str(user.id), "invoice.created", invoice)
dispatch_btcpay_webhooks(db, str(user.id), "InvoiceCreated", invoice)
status_name, additional_status = _btcpay_status(invoice)
amount, currency, _ = _btcpay_amount_currency(invoice)
expiration_time = _epoch_seconds(invoice.expires_at)
+47 -1
View File
@@ -6,6 +6,7 @@ import json
import logging
import time
from datetime import datetime, timezone
from urllib.parse import urljoin
import requests
from requests import RequestException
@@ -16,6 +17,8 @@ from .security import decrypt_secret
logger = logging.getLogger(__name__)
_REDIRECT_STATUSES = {301, 302, 303, 307, 308}
def dispatch_btcpay_webhooks(
db: Session,
@@ -51,8 +54,25 @@ def dispatch_btcpay_webhooks(
headers = {
"BTCPay-Sig": f"sha256={signature}",
"Content-Type": "application/json",
"User-Agent": "xmrcheckout-btcpay-webhook/1.0",
}
requests.post(hook.url, data=body, headers=headers, timeout=5)
response = _post_with_redirects(
hook.url,
data=body,
headers=headers,
timeout=5,
)
if response is None:
continue
if response.status_code >= 400:
logger.warning(
"BTCPay webhook delivered non-success status",
extra={
"webhook_id": str(hook.id),
"event": event_type,
"http_status": response.status_code,
},
)
except RequestException as exc:
logger.warning(
"BTCPay webhook delivery failed",
@@ -109,3 +129,29 @@ def _after_expiration(invoice: Invoice) -> bool:
def _sign_payload(body: bytes, secret: str) -> str:
return hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
def _post_with_redirects(
url: str,
*,
data: bytes,
headers: dict[str, str],
timeout: int,
max_redirects: int = 3,
) -> requests.Response | None:
current_url = url
for _ in range(max_redirects + 1):
response = requests.post(
current_url,
data=data,
headers=headers,
timeout=timeout,
allow_redirects=False,
)
if response.status_code not in _REDIRECT_STATUSES:
return response
location = response.headers.get("Location")
if not location:
return response
current_url = urljoin(current_url, location)
return None
+1
View File
@@ -178,6 +178,7 @@ def _reconcile_invoices(service: MoneroWalletService) -> None:
dispatch_btcpay_webhooks(
db, str(user.id), "InvoiceReceivedPayment", invoice
)
dispatch_btcpay_webhooks(db, str(user.id), "InvoicePaidInFull", invoice)
dispatch_btcpay_webhooks(
db, str(user.id), "InvoiceProcessing", invoice
)