diff --git a/examples/python/servers/fastapi/main.py b/examples/python/servers/fastapi/main.py
index cecdad2f0..0645e144d 100644
--- a/examples/python/servers/fastapi/main.py
+++ b/examples/python/servers/fastapi/main.py
@@ -161,27 +161,42 @@ async def dynamic_price_middleware(request: Request, call_next):
@app.get("/")
async def health_check():
- """Health check endpoint that also serves as site metadata"""
+ """Health check endpoint that also serves as site metadata for x402scan"""
from fastapi.responses import HTMLResponse
- # Return HTML with meta tags for better x402scan integration
+ # Return HTML with meta tags for x402scan to scrape
+ # x402scan extracts: title, description, favicon, og:* tags
html_content = """
+
+
Virtuals Protocol - ACP Job Payment
-
-
-
+
+
+
+
+
+
-
+
+
+
+
+
- Virtuals Protocol - ACP Job Payment
+ 🤖 Virtuals Protocol - ACP Job Payment
This is an x402 payment service for Virtuals Protocol ACP jobs.
Status: ✓ Operational
+
+ Available Endpoints:
+
+ /acp-budget - Pay for ACP job budget (supports dynamic pricing via X-Budget header)
+
"""
@@ -191,11 +206,14 @@ async def health_check():
@app.get("/favicon.ico")
async def favicon():
"""Serve favicon for x402scan to display as resource icon"""
+ from fastapi.responses import Response
+
favicon_path = os.path.join(os.path.dirname(__file__), "static", "favicon.ico")
if os.path.exists(favicon_path):
return FileResponse(favicon_path, media_type="image/x-icon")
- # Return a default response if favicon doesn't exist
- return FileResponse(favicon_path, media_type="image/x-icon", status_code=404)
+
+ # Return 404 if favicon doesn't exist
+ return Response(status_code=404, content="Favicon not found")
@app.api_route("/acp-budget", methods=["GET", "POST"])
diff --git a/python/x402/src/x402/fastapi/middleware.py b/python/x402/src/x402/fastapi/middleware.py
index de601bc66..618fb3e94 100644
--- a/python/x402/src/x402/fastapi/middleware.py
+++ b/python/x402/src/x402/fastapi/middleware.py
@@ -20,12 +20,17 @@
PaymentPayload,
PaymentRequirements,
Price,
+ SettleResponse,
x402PaymentRequiredResponse,
PaywallConfig,
SupportedNetworks,
HTTPInputSchema,
)
+import os
+import httpx
+
+
logger = logging.getLogger(__name__)
@@ -206,10 +211,54 @@ def x402_response(error: str):
# Settle the payment
try:
-
- settle_response = await facilitator.settle(
- payment, selected_payment_requirements
- )
+ custom_facilitator_url = os.getenv("CUSTOM_FACILITATOR_API_URL", "")
+ custom_facilitator_succeeded = False
+ custom_facilitator_response = None
+
+ if custom_facilitator_url:
+ try:
+ url = f"{custom_facilitator_url.rstrip('/')}/api/x402/facilitators/settle"
+ headers = {"x-payment": payment_header}
+ async with httpx.AsyncClient(timeout=60.0) as client:
+ custom_facilitator_response = await client.post(url, headers=headers)
+ custom_facilitator_response.raise_for_status()
+ custom_facilitator_succeeded = True
+ logger.info("Custom facilitator settle succeeded")
+ except Exception as e:
+ logger.warning("Custom facilitator settle POST failed: %s, falling back to default facilitator", e)
+ custom_facilitator_succeeded = False
+
+ # Fallback to default facilitator if custom facilitator wasn't used or failed
+ if not custom_facilitator_succeeded:
+ settle_response = await facilitator.settle(
+ payment, selected_payment_requirements
+ )
+ else:
+ # If custom facilitator succeeded, we still need a settle_response for the response headers
+ # Create a minimal success response
+ try:
+ payer = payment.payload.authorization.from_
+ except (AttributeError, KeyError):
+ payer = ""
+
+ # Extract transaction hash from custom facilitator response
+ transaction_hash = ""
+ try:
+ if custom_facilitator_response:
+ response_data = custom_facilitator_response.json()
+ if isinstance(response_data, dict) and "data" in response_data:
+ data = response_data["data"]
+ if isinstance(data, dict) and "transactionHash" in data:
+ transaction_hash = data["transactionHash"]
+ except (AttributeError, KeyError, ValueError, httpx.DecodeError) as e:
+ logger.warning("Failed to extract transactionHash from custom facilitator response: %s", e)
+
+ settle_response = SettleResponse(
+ success=True,
+ transaction=transaction_hash,
+ network=selected_payment_requirements.network,
+ payer=payer,
+ )
if settle_response.success:
response.headers["X-PAYMENT-RESPONSE"] = base64.b64encode(