fix?!??!?!

This commit is contained in:
C committed 2026-01-03 12:50:36 -08:00
1 parent bd8dd3b93d
commit d95ce1dc7a
1 file changed
+86 -39
+86 -39
View File
@@ -1,46 +1,57 @@
""" """
Production webhook server using FastAPI + Gunicorn/Uvicorn Production webhook server using FastAPI + Gunicorn/Uvicorn
Receives Square webhooks with signature verification Receives Square webhooks with signature verification
""" """
import os import os
import json import json
import asyncio import asyncio
from typing import Dict, Any from typing import Dict, Any
from fastapi import FastAPI, Request, Response, status from fastapi import FastAPI, Request, Response, status
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
import dotenv import dotenv
# Load environment variables # Load environment variables
dotenv.load_dotenv() dotenv.load_dotenv()
# Square webhook validation # Square webhook validation
from square.utils.webhooks_helper import verify_signature from square.utils.webhooks_helper import verify_signature
# Configuration # Configuration
SQUARE_WEBHOOK_SIGNATURE_KEY = os.getenv("SANDBOX_SQUARE_WEBHOOK_INVENTORY_ID") SQUARE_WEBHOOK_SIGNATURE_KEY = os.getenv("SANDBOX_SQUARE_WEBHOOK_INVENTORY_ID")
SQUARE_WEBHOOK_URL = os.getenv("SQUARE_WEBHOOK_URL") SQUARE_WEBHOOK_URL = os.getenv("SQUARE_WEBHOOK_URL")
# In-memory store for processed event IDs (idempotency) # Debug: Print configuration (redact key)
processed_event_ids = set() if SQUARE_WEBHOOK_SIGNATURE_KEY:
MAX_STORED_EVENT_IDS = 10000 print(f"[CONFIG] Signature key loaded: {SQUARE_WEBHOOK_SIGNATURE_KEY[:10]}...")
else:
print("[CONFIG] WARNING: SQUARE_WEBHOOK_SIGNATURE_KEY not set!")
# Create FastAPI app if SQUARE_WEBHOOK_URL:
app = FastAPI(title="StudSpider Webhook Server") print(f"[CONFIG] Webhook URL: {SQUARE_WEBHOOK_URL}")
else:
print("[CONFIG] WARNING: SQUARE_WEBHOOK_URL not set!")
# In-memory store for processed event IDs (idempotency)
processed_event_ids = set()
MAX_STORED_EVENT_IDS = 10000
# Create FastAPI app
app = FastAPI(title="Studspider")
@app.get("/") @app.get("/")
async def root(): async def root():
"""Landing page""" """Landing page"""
return { return {
"service": "StudSpider Webhook Server", "service": "Studspider",
"status": "running", "status": "running",
"endpoints": {"webhook": "/hook (POST)", "health": "/health (GET)"}, "endpoints": {"webhook": "/hook (POST)", "health": "/health (GET)"},
} }
@app.get("/health") @app.get("/health")
async def health_check(): async def health_check():
"""Health check endpoint for monitoring""" """Health check endpoint for monitoring"""
return { return {
"status": "healthy", "status": "healthy",
@@ -50,10 +61,10 @@ async def health_check():
} }
@app.post("/hook") @app.post("/hook")
async def webhook_handler(request: Request): async def webhook_handler(request: Request):
""" """
Handle Square webhook events Handle Square webhook events - MUST respond within 2 seconds!
Square sends POST requests with: Square sends POST requests with:
- x-square-hmacsha256-signature header - x-square-hmacsha256-signature header
@@ -61,14 +72,22 @@ async def webhook_handler(request: Request):
- event_id for idempotency - event_id for idempotency
""" """
try:
# Log incoming request
print(f"\n[WEBHOOK] Incoming request from {request.client.host}")
print(f"[WEBHOOK] Method: {request.method}")
print(f"[WEBHOOK] URL: {request.url}")
print(f"[WEBHOOK] Headers: {dict(request.headers)}")
# Read raw body for signature verification # Read raw body for signature verification
body = await request.body() body = await request.body()
body_str = body.decode("utf-8") body_str = body.decode("utf-8")
print(f"[WEBHOOK] Body length: {len(body_str)} bytes")
# Get signature header # Get signature header
signature_header = request.headers.get("x-square-hmacsha256-signature", "") signature_header = request.headers.get("x-square-hmacsha256-signature", "")
# Verify Square webhook signature # Verify Square webhook signature (if configured)
if SQUARE_WEBHOOK_SIGNATURE_KEY and SQUARE_WEBHOOK_URL: if SQUARE_WEBHOOK_SIGNATURE_KEY and SQUARE_WEBHOOK_URL:
if not signature_header: if not signature_header:
print( print(
@@ -79,6 +98,12 @@ async def webhook_handler(request: Request):
content={"error": "Missing signature header"}, content={"error": "Missing signature header"},
) )
# Debug signature verification inputs
print(f"[DEBUG] Signature header: {signature_header[:20]}...")
print(f"[DEBUG] Signature key (first 10 chars): {SQUARE_WEBHOOK_SIGNATURE_KEY[:10]}...")
print(f"[DEBUG] Notification URL: {SQUARE_WEBHOOK_URL}")
print(f"[DEBUG] Body (first 100 chars): {body_str[:100]}...")
try: try:
is_valid = verify_signature( is_valid = verify_signature(
request_body=body_str, request_body=body_str,
@@ -88,7 +113,13 @@ async def webhook_handler(request: Request):
) )
if not is_valid: if not is_valid:
print(f"[ERROR] Invalid webhook signature from {request.client.host}") print(
f"[ERROR] Invalid webhook signature from {request.client.host}"
)
print(f"[ERROR] This usually means:")
print(f"[ERROR] 1. SQUARE_WEBHOOK_URL doesn't match Square Dashboard exactly")
print(f"[ERROR] 2. SQUARE_WEBHOOK_SIGNATURE_KEY is incorrect")
print(f"[ERROR] 3. Request body was modified in transit")
return JSONResponse( return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
content={"error": "Invalid signature"}, content={"error": "Invalid signature"},
@@ -96,10 +127,10 @@ async def webhook_handler(request: Request):
print(f"[INFO] ✓ Webhook signature verified from {request.client.host}") print(f"[INFO] ✓ Webhook signature verified from {request.client.host}")
except Exception as e: except Exception as e:
print(f"[ERROR] Signature verification failed: {e}") print(f"[ERROR] Signature verification exception: {e}")
return JSONResponse( return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
content={"error": "Signature verification failed"}, content={"error": f"Signature verification failed: {str(e)}"},
) )
else: else:
print( print(
@@ -112,7 +143,8 @@ async def webhook_handler(request: Request):
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
print(f"[ERROR] Invalid JSON from {request.client.host}: {e}") print(f"[ERROR] Invalid JSON from {request.client.host}: {e}")
return JSONResponse( return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST, content={"error": "Invalid JSON"} status_code=status.HTTP_400_BAD_REQUEST,
content={"error": "Invalid JSON"},
) )
# Check idempotency (event_id) # Check idempotency (event_id)
@@ -157,17 +189,32 @@ async def webhook_handler(request: Request):
) )
) )
# Return immediate response
print(f"[INFO] 200 OK response sent for event {event_id_val}")
return JSONResponse(status_code=status.HTTP_200_OK, content=response_data)
except Exception as e:
# Catch-all for any unexpected errors - still respond quickly
print(f"[ERROR] Unexpected error in webhook handler: {e}")
import traceback
traceback.print_exc()
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={"error": "Internal server error"},
)
# Return immediate response # Return immediate response
return JSONResponse(status_code=status.HTTP_200_OK, content=response_data) return JSONResponse(status_code=status.HTTP_200_OK, content=response_data)
async def process_webhook( async def process_webhook(
webhook_type: str, webhook_type: str,
event_id: str, event_id: str,
merchant_id: str, merchant_id: str,
data: Dict[str, Any], data: Dict[str, Any],
client_ip: str, client_ip: str,
): ):
""" """
Process webhook data asynchronously after responding to Square Process webhook data asynchronously after responding to Square
@@ -211,7 +258,7 @@ async def process_webhook(
print(f"[ERROR] Failed to process webhook {event_id}: {e}") print(f"[ERROR] Failed to process webhook {event_id}: {e}")
if __name__ == "__main__": if __name__ == "__main__":
# For development/testing - use uvicorn directly # For development/testing - use uvicorn directly
import uvicorn import uvicorn