#andre-herndon_webhooks

1 messages Β· Page 1 of 1 (latest)

still jayBOT
#

πŸ‘‹ 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.

forest charm
#

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()

uneven crypt
#

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

forest charm
#

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?

uneven crypt
#

yes

#

yes

forest charm
#

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

uneven crypt
#

THank you so much

#

No signatures found matching the expected signature for payload

forest charm
#

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.

uneven crypt
#

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
still jayBOT
uneven crypt
#

why is stripe making it so complicated , that could have been simplified.

forest charm
#

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.

uneven crypt
#

so you are saying just compare payload from local and payload from prod

#

like this?

hard nymph
#

Hello! πŸ‘‹ give me some time to catch up

uneven crypt
#

The keys, nested fields, and structure are fully aligned. If you're writing validation logic (e.g. schema validation), both payloads are structurally identical.

hard nymph
#

@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?

uneven crypt
#

google cloud platform

#

it will be found in the textPayload

#

but should i pass it in to stripe .decode("utf-8")??

hard nymph
uneven crypt
#

hmm ya

#

i think there is a serious bug with our account ive done everthing

hard nymph
#

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?

uneven crypt
#

yes this is prod

hard nymph
#

what about local?

uneven crypt
#

exactly this works perfectly

#

are we able to maybe screenshare maybe we can go faster?

hard nymph
#

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

uneven crypt
#

no no

#

its only textpayload

uneven crypt
hard nymph
#

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?

[0] https://docs.stripe.com/webhooks/quickstart?lang=python

uneven crypt
#

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.

hard nymph
#

when you *cache the original bytes in middleware *, you are already altering the request body

uneven crypt
#

so dont do this?

#

im sorry but this is horrible...

hard nymph
#

yes

uneven crypt
#

why isnt there documentation, what am i doing wrong, why isnt it working...

hard nymph
uneven crypt
#

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

hard nymph
#

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.

uneven crypt
#

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