#andre-herndon_webhooks
1 messages Β· Page 1 of 1 (latest)
π Welcome to your new thread!
β²οΈ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.
β±οΈ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.
π This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1387925889370816665
π Have more to share? Add more details, code, screenshots, videos, etc. below.
Hello! π
Do you have an example code snippet of the Webhook endpoint in question?
Are you able to log the contents of the payload before passing into the stripe.Webhook.construct_event()
yes
@router.post("/v1/tenant/webhook", tags=["Webhooks"])
async def tenant_webhook(request: Request, db: Any = Depends(get_db)):
"""Enhanced webhook with proper security and error handling."""
payload = await request.body()
sig_header = request.headers.get("stripe-signature")
print("payload", payload.decode("utf-8"))
print("sig_header", sig_header)
print("stripe_webhook_secret", stripe_webhook_secret)
print("date time", datetime.now(timezone.utc))
if not stripe_webhook_secret:
raise HTTPException(status_code=500, detail="Webhook secret not configured.")
try:
event = stripe.Webhook.construct_event(
payload, sig_header, stripe_webhook_secret
)
except (ValueError, stripe.SignatureVerificationError) as e:
logger.warning("Invalid webhook signature or payload: %s", e)
raise HTTPException(
status_code=400, detail="Invalid signature or payload."
) from e
logger.error("π Received webhook event: %s", event["type"])
if event["type"] in supported_event_types:
try:
await process_checkout_completion(event["data"]["object"], db)
except Exception as e: # pylint: disable=broad-exception-caught
logger.error(
"Webhook processing failed for event %s: %s",
event["id"],
e,
exc_info=True,
)
raise HTTPException(
status_code=500,
detail="Internal server error during webhook processing.",
) from e
return {"status": "success"}
i just added the date time just not because im trying to test if the gcp server clock time is somehow out of sync with the stripe time and if its >5 mins out of sync
Looking it over, were you able to dump the payload succesfully on a test run?
Can you also confirm you're using HTTPS on your production serrver?
Your endpoint code is working for me locally as well. Suspect its something with your servers configuration. Gonna ask if anyone on my team has any ideas
I think it would be valuable to send the same event to your production endpoint and your local one dumping the raw payload for both so they can be compared. If your server configuration is tampering with the payload this would help verify that fact.
It's also worth double checking the secret one more time.
okay
also do i really need this?
try:
event = stripe.Webhook.construct_event(
payload, sig_header, stripe_webhook_secret
)
except (ValueError, stripe.SignatureVerificationError) as e:
logger.warning("Invalid webhook signature or payload: %s", e)
raise HTTPException(
status_code=400, detail="Invalid signature or payload."
) from e
why is stripe making it so complicated , that could have been simplified.
constructing the event object makes working with the event data easier and ensures it's can be transmitted securely.
I think it's likely the payload is being tampered with in someway but dumping the payload to cross reference would make it a lot easier.
My team member Aure will be taking over just getting them caught up on the thread.
so you are saying just compare payload from local and payload from prod
like this?
Hello! π give me some time to catch up
The keys, nested fields, and structure are fully aligned. If you're writing validation logic (e.g. schema validation), both payloads are structurally identical.
@uneven crypt you are printing your payload with print("payload", payload.decode("utf-8")) β could you print and check the payload body and type without specifying payload.decode("utf-8")?
It is possible the payload in your test v.s. production aren't actually the same, but the printing with payload.decode("utf-8") makes them the same.
Also, which production hosting provider are you using?
google cloud platform
it will be found in the textPayload
this is even the same scheme
but should i pass it in to stripe .decode("utf-8")??
You shouldn't need to do this. The general rule of thumb is using the raw body: https://docs.stripe.com/webhooks/signature#check-the-request-body
so when you simply print out the raw payload on local v.s. production, did you get the exactly same value?
You provided this b'{\\n \"id\": \"evt_1ReGXKREpfOE822NJGRZ2VRS\",\\n... β is this production?
yes this is prod
what about local?
exactly this works perfectly
are we able to maybe screenshare maybe we can go faster?
we don't support screenshare, I'm sorry.
also I just compared the two payloads
your production payload is being is wrapped in a Google Cloud Run log entry with additional metadata like instanceId, commit-sha, etc. GCP (Google Cloud Platform often has an additional middleware layer) and wrap over the payload, so this could cause difference in payload between local and production
so like this is production one
okay i've looked at this with my team and its really quite odd that a setup that passes in local fails in production, assuming the exact same code is being used. What you could do is
- Double (triple!) check where the code and configuration on middleware is exactly the same on local vs production. Our suspicion is that your production actually needs to unwrap the GCP specific payload to take the βrealβ payload, while your local doesnβt, which is where we're seeing the discrepancy
- FastAPI seems to be Python, are you using our Python SDK? What if you download our webhook example code (the full project) [0] and try it on local vs production?
ai is saying do something like this?
To ensure the body is preserved as-is, you can cache the original bytes in middleware before anything else touches it.
when you *cache the original bytes in middleware *, you are already altering the request body
yes
why isnt there documentation, what am i doing wrong, why isnt it working...
We do have this page [0] that says to specially to handle the raw request body as-is
[0] https://docs.stripe.com/webhooks/signature#retrieve-the-raw-request-body
what if i have this middleware:
app.add_middleware(
CORSMiddleware,
allow_origins=[""], # TODO revsit CORS origin policy
allow_credentials=True,
allow_methods=[""], # Allows all methods
allow_headers=["*"], # Allows all headers
max_age=300, # cache pre-flight 5 min
)
Yes but nothing about python fastapi
we won't be able to give an exhaustive list of all frameworks but the general guidance is the raw request needs to be handled.
I can't advice on the CORSMiddleware, because its not something we provide in our guide. You'll need to test.
hmmm
so i did something like this: try:
event = stripe.Webhook.construct_event(
payload, sig_header, stripe_webhook_secret
)
except ValueError as e:
logger.warning("Malformed payload received: %s", e)
raise HTTPException(status_code=400, detail="Malformed payload.") from e
except stripe.error.SignatureVerificationError as e:
logger.warning("Invalid Stripe signature: %s", e)
raise HTTPException(status_code=400, detail="Invalid Stripe signature.") from e
if this is a good way to dividie the logs to see if its the payload or osmething else: Invalid Stripe signature: No signatures found matching the expected signature for payload
so maybe its the signature