#kenny_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/1245733139054202976
๐ Have more to share? Add more details, code, screenshots, videos, etc. below.
Hello
Hi, here's my webhook code
import stripe
from fastapi import APIRouter, Header, HTTPException, Request
from src.lib.config import get_config
router = APIRouter(prefix="/stripe/webhooks")
@router.post("")
async def handle_webhook(
request: Request,
site_id: str,
stripe_signature: str = Header(None),
):
config = get_config()
signing_secret = config.STRIPE_SIGNING_SECRET
data = await request.body()
try:
event = stripe.Webhook.construct_event(
payload=data, sig_header=stripe_signature, secret=signing_secret
)
except ValueError as e:
print(f"Invalid signature: {e}")
raise HTTPException(status_code=400, detail="Invalid signature")
except stripe.SignatureVerificationError as e:
print(f"Signature verification failed: {e}")
raise HTTPException(
status_code=400, detail="Signature verification failed"
)
except Exception as e:
print(f"Error in stripe webhook: {e}")
raise HTTPException(status_code=400, detail=str(e))
event_type = event["type"]
if event_type == "invoice.paid":
checkout_session = event["data"]["object"]
subscription_id = checkout_session.get("subscription")
if not subscription_id:
return {"error": "subscription_id not found in event data"}
currency = checkout_session["currency"].upper()
amt_paid = checkout_session["amount_paid"]
metadata = checkout_session["metadata"]
salestracking_id = metadata.get("stId")
print(
f"Salestracking ID: "
f"{salestracking_id}, "
f"Amount Paid: {amt_paid}, "
f"Currency: {currency}"
f"Site ID: {site_id}"
)
# TODO: Insert the data into the clickhouse DB as an event.
Yeah the issue is you likely don't have the raw body here from data = await request.body()
What framework are you using here?
fastapi
I pretty much copy and pasted this code from how I handle standard webhook events directly from my stripe account with the key different being the signing key used
What is the exact error message?
INFO: 127.0.0.1:50749 - "POST /v0/stripe/webhooks?site_id=69 HTTP/1.1" 400 Bad Request
Signature verification failed: No signatures found matching the expected signature for payload
This is the error I would always face when I put the wrong webhook signing key in
in my production app
In this case, I put my app's signing key. I don't know if that's right but I don't know any other way i'd do it
Okay one sec, let me grab a colleague more familiar with this flow in Stripe Apps
great, thanks
Hello jumping in to help out here
Do you know if the framework you're using (fastapi) converts any incoming requests payload to JSON automatically?
I don't, but if doing that would break it, I'll show you my exact webhook fastapi code I have in production that has worked with over 1k transactions so far. Note that this isn't app related:
Oh also, Stripe CLI prints out its own webhook secret. Is that the one you're using in your code?
When you run stripe listen it should print out the secret
No, I've been using the app's signing secret
I'm trying to make it so that people can connect my app to their stripe and then my server can listen to their webhooks
I think there is a misunderstanding about the flow here. Are you following any docs for this?
I can't find any. But I know that this app does it succesfully: https://marketplace.stripe.com/apps/promotekit
When making what I shared above I was following this
https://docs.stripe.com/stripe-apps/build-backend?public-private=private#receiving-events-webhooks
but it still doesn't fit perfectly for what I thought would be simple to implement
I see. Let me elaborate. The webhook secret value is unique per endpoint. When you run Stripe CLI, it generates its own secret which would then be used to verify signature of the event that Stripe sends.
You shouldn't be using app's signing secret in this case.
Then how does the promotekit and slack's stripe app listen to events without me having to provide them with my webhook signing key?
The guide you linked here shows you two flows: https://docs.stripe.com/stripe-apps/build-backend?public-private=private#receive-events
One for when you're testing the app on your own account versus when you publish the app on the marketplace and someone installs it on their own stripe account.
If you look at "Public listing on App Marketplace" portion of the guide, it explains how you'd need to "Listen to events on Connected accounts" and add specific event type permissions to your app.
In which case, Stripe will send any events (matching your permissions) that get generated on the Stripe accounts that installs your app.
well i tried that too
but it never reached my endpoint
This was my trigger: stripe trigger --stripe-account acct_1PEt7TKQ4RvXxzCu invoice.paid
and my listen: stripe listen --forward-connect-to "localhost:8000/v0/stripe/webhooks?site_id=69"
I looked most recent invoice.paid event on acct_1PEt7TKQ4RvXxzCu, I do see it was sent to your Stripe CLI webhook.
But really your current issue is triggered by you using the wrong webhook secret
When I run the commands above nothing reaches my server so how could that be a webhook secret problem?
Not talking about the above issue. I meant to say your original issue with signature verification failing..
Ah yeah
Oh wait
But having an individual webhook key for each wouldnt be viable here because then my client would need to provide me with their webhook secret
acct_1PEt7TKQ4RvXxzCu isn't connected to any platforms
I want it to be one-click like promotekit
But having an individual webhook key for each wouldnt be viable here because then my client would need to provide me with their webhook secret
that won't be the case when you list your app in production
In production, you'd only have one webhook endpoint URL where you'd receive the events
How would I distinguish between the accounts?
and where would I put that production webhook endpoint url
When a Stripe account installs your app from the marketplace, Stripe establishes a connection between that account and your account.
In this scenario, your account becomes a platform and the account installing your app becomes a connected account
You'd set your production webhook URL when you create a webhook endpoint
https://dashboard.stripe.com/webhooks
ok that makes sense
so the webhooks event will contain the account id it's associated with?
Correct
okay, so my permissions are set up correctly and the app as I currently have it set up will properly proxy the webhooks from the ppl who connect their stripe acct to my account right?
so all webhooks travel through a unified endpoint
Yup.. As long as the endpoint you've created is listening to events on the connected account. Like I mentioned earlier: #1245733139054202976 message
So that's done through permissions?
right now my app permissions are as follows:
"permissions": [
{
"permission": "webhook_read",
"purpose": "Tracks sales and sales revenue into SalesTracking"
}
],
There are two parts to it.
Configuring permissions lets your customer (Stripe accounts that install your app) know about the type of events your app will have access to.
The second part is that you'd need to create a webhook on your platform account that listens to the events that get generated on connected accounts. You'd configure a connect type webhook endpoint when you create a new one through: https://dashboard.stripe.com/webhooks
This selection I presume
and for testing I'd just use the singular webhook signing key that is generated for me?
thanks for the help