Building security-first Stripe integrations
Advancing developer craft
Durée
Remplissez le formulaire pour voir la vidéo en entier
This session covers the most common security pitfalls when building Stripe integrations—exposed keys, misconfigured webhooks, authentication gaps—and how to design with a secure-by-default approach using Stripe’s latest primitives.
Speakers
Caitriona Kelly, Engineering Manager, Stripe
Pranav Punjabi, Product Lead, Security Infra, Stripe
PRANAV PUNJABI: Hello. Good afternoon, everyone. Thank you so much for making it out to a post-lunch security talk. Impressive. I am Pranav. I’m a security product manager at Stripe.
CAITRIONA KELLY: And I’m Caitriona, a security engineering manager at Stripe.
PRANAV PUNJABI: A few months ago, a developer shared their story on social media. They used an AI coding agent to build their startup. It shipped fast and it looked really great. But what really happened was that the agent placed this Stripe secret key directly in the frontend code. Their SK_live key sitting in the page source, visible to anyone who just clicks “view source” in the browser. The attackers found it and they started making API calls against their account. Now, this is not an edge case. With the rise of vibe coding and AI agents, we are actually seeing these new kinds of exposure. Agents will check codes into repos and you do not review every single line of it. And they will store keys in places that we normally never would. And attackers are actively harvesting these credentials at scale.
CAITRIONA KELLY: And it’s not just AI. Engineers can make the same mistakes. We’ve seen API keys in frontend source code, CI logs, Slack messages. In one case, a customer fell foul of a security vulnerability through their frontend dependencies, and this gave an attacker access to all of their environment variables. This is where they’re storing the secret key. And unfortunately were victim to a key takeover. The attack surface is evolving. Here’s the reality. If an attacker gets access to a key with full access, they can create payments, issue refunds, access customer data, change the location of your payouts. The financial impact can be massive, and it all stems from a credential that was exposed, overprivileged, or both.
PRANAV PUNJABI: So today we will help you make sure that you do not become the next case study. What we’ll do is we’ll walk through some of the security primitives that Stripe offers and show you exactly how to use them. Not just what to do, but why it matters and how to do it. And we’ll do so with some live demos that you can replicate in your account today. So for this, we’re going to follow a fictional company. Let’s call it Cool Hats and Tees. And by the way, that is Caitriona’s side gig. She insists it’s fictional, but the Shopify store says otherwise. We’ll start with some basic things to get right and then increase the complexity. At each stage of growth, we’ll see that the security challenges will change and Stripe has the tools for every stage. We’ll cover three basic areas today. We’ll focus on locking down who can access your account.
Then we’ll focus on managing the secrets that actually power your Stripe integration. And then eventually we’ll focus on defending your system at runtime. And we’ll share some of the practices that we follow internally at Stripe because we do eat our own cooking on this.
All right. Let’s start where every business starts. Let’s focus on identity and access. So Cool Hats and Tees has just launched. It has two founders, a laptop, and the most important thing, it has a Stripe account. And business is good because the business model is pretty simple. It sells hats and tees. But before we even write a single line of integration code, we need to think about the front door. Who can log into our Stripe Dashboard and how do they prove they are who they say they are? If an attacker logs into your Dashboard, your API keys, your customer data, your payout settings, all of it is exposed. And nothing else you build really matters if this front door is left open.
CAITRIONA KELLY: Step one for Cool Hats and Tees. We’re just going to enforce 2FA for everyone on the account. It’s a single toggle in the team and security setting page in the Dashboard. Now the question you’ll be asking yourself is, which second factor? And that’s the right question, because they’re not all equal. SMS codes can be intercepted through SIM swapping. An attacker, they call your carrier, social engineers their way into porting your number, and now they have your codes. Authenticator apps are better, but they’re still phishable. A login page that looks like Stripe but isn’t, and you type in your code. Now attackers can relay those codes in real time. So what can you do? Passkeys block this category of attack. They bind authentication to the device and the origin domain. So a login page that looks like Stripe, but it isn’t, can’t trigger that passkey.
The protocol itself refuses. It’s not about you being careful. It’s about blocking the attack by design. At Stripe, we use passkeys and you can enable passkeys for your account in your user settings. Takes about 10 seconds.
PRANAV PUNJABI: Yep. It’s that easy. And here’s a stat that should change how you think about access management. 31% of former employees still have access to company SaaS applications after they leave. Nearly a third. Now imagine somebody who does not work for you anymore, still has access to the Stripe account, can log in, can view customer data, and can potentially take actions. Now, Cool Hats and Tees is growing. 15 people have been hired and they all need access to the Stripe Dashboard. But when you manage access account by account, across Stripe, across your cloud provider, and across every SaaS tool, something is bound to get missed. And the fix here is to centralize this. When someone leaves and you deactivate them in your identity provider, their Stripe access is revoked instantly. So all you have to do is worry about one place to update and have just one source of truth through trust.
CAITRIONA KELLY: So the critical detail that gets missed with single sign-on is when you get started, you’re in optional mode. And this means you can fall back to passwords. This is great when you’re getting started, so you don’t get accidentally logged out of your account. But when you get it working, move to required mode. This is what gives you the centralized control because it disables the password fallback. Now, SCIM is a standard for controlling user management between identity providers and your apps. And when you layer SCIM on top of your Stripe integration, you can handle your user management like credentialing and decredentialing. So a new hire starts. They get the right Stripe role. Someone leaves, their access is revoked, but there is no manual steps. There was no account left behind. At Stripe, we use these tools internally, and we believe they’re so table stakes, we make them available to all users.
PRANAV PUNJABI: So our first takeaway for today is go to your Dashboard and enforce 2FA. Then set up a passkey. It is a single highest leverage security action that you can take in under one minute. And if you do have a growing team, then start the SSO conversation early. It does take time to set up, but the payoff is enormous here.
All right. The front door is now locked. Now let’s talk about the thing that most of you interact with, I’m guessing, every day, and the thing that was at the center of my story when I first started. That is your API keys.
Stripe does take API keys very seriously. When a key compromise happens, dedicated teams work around the clock to mitigate the exposure and contain the damage and get the accounts back to safety. We’re also investing significantly in many areas directly related to API key security. But the reality is that the key compromises occur outside of Stripe’s boundary. It could be a repo, a log, a misconfigured agent, so you are ultimately responsible for the security of your Stripe API keys. But the good news is that you’re completely in control of the most effective defense, and that is securing your integration. So we’ll show you through a quick demo. Oh, actually, we’ll pause for that.
CAITRIONA KELLY: When I set up Cool Hats and Tees, I did what most people do. I used a standard secret key and I got moving. It was fast, it was simple, and there was no friction. But let’s look at what that key can do. So payments, refunds, alerts. Yeah, makes sense. Payouts, app secrets? Does my payments code really need to change the location of my payouts? Probably not, but that’s what it has access to. Now, we actually monitor for exposed keys and we’ll notify you if we find one. But the best defense is to limit the damage a compromised key can do. And this is what restricted keys give you.
PRANAV PUNJABI: Yep. Every restricted key is purpose-built for very specific jobs. For example, my checkout service here will get the RK live checkout key with only write access to Payment Intents and read access to customers. My reporting service will get the RK live reporting key with only read access to reports data. Everything else will be denied by default. And here’s a benefit that is very easy to miss. If an attacker does get hold of one of these keys, they do not know what to do with it. So they start probing. They start hitting different endpoints to figure out what works, what doesn’t work. Now what this does is it starts lighting up 403 error codes in your API logs. Now, this should be your early warning signal. With the standard key, you never get this signal. An attacker just gets access to your key and everything works for them silently.
Now I do know what some of you are thinking. My standard key works just fine. I don’t want to break anything. I’ll be on the hook for that. So let us show you how switching to a restricted key is actually a configuration change and not a code change.
CAITRIONA KELLY: Okay. So here I am in Cool Hats and Tees dashboard. I’m on the API keys page and I’m going to create a key that’s just going to power payments for my website. So I’m going to create a restricted key. And I have a couple of options here. Now the default is to power an integration I’ve built. Sounds about right. But recently launched, you can also create restricted keys that are for your agents. This allows you to scope what permissions you want your agent to have and also allows for better tracing. Now, I’m not an agent. So let’s go with powering integration I’ve built and I also get some other options. So these are kind of groupings or templates based on typical type behaviors, but I want to make a really tightly scoped key. So I’m going to choose my own. I’m going to name it payments.
PRANAV PUNJABI: So as you see, Caitriona is creating a payments key. She doesn’t need a lot of permissions for this. I think she only needs write permission for Payment Intents. Notably, everything else will default to none. So since we only need one permission, we will quickly scroll past all the other permissions and go ahead and create our key.
CAITRIONA KELLY: Okay. Here’s my key. I’m in test mode, so it has “test” in the name. If I was in live mode, it would have “live” in the name and I could only view it once. So I’m going to copy it and now I’m going to move it over to my secrets manager.
PRANAV PUNJABI: For our demo today, we are using 1Password locally because it doesn’t have network requirements and we don’t want to mess with the demo gods today. For you guys, there’s a number of options for secrets managers that you can use out there. But the most important thing to note here is do not store your secrets as an environment variable in a conflict file.
CAITRIONA KELLY: So let’s test this out. So I’m going to buy with my test card for my awesome new T-shirt. It’s a very professional-looking website, obviously. And now let’s try refund that, although I’m not sure why you’d want a refund for that amazing T-shirt. Now you saw when I created the key, I gave payments access, but I didn’t give refunds. So this is as expected. And when we go to the Dashboard and let’s look at our logs, we can see what Pranav was talking about earlier. So I can see the 200 request for the payment that’s gone through and I see a 403 for the refund that wasn’t allowed. Now I have a choice to make. I can choose to give access to the refund on the key I just created, or I can create a dedicated key just for refunds. Whatever you want to do, depends on your architecture.
Now, getting started with restricted keys—it can be daunting. Easiest thing to do, just make a list of all of the APIs that you’re using. You have the API request logs for this, but you can also just point an agent at your code. It’ll pull everything. You can cross reference with our docs and all our docs are LLM ingestible. Or use our new templates feature. It has some good groupings.
Now, taking a step back. I created a restricted key really easily. I moved it to my secrets manager. I just needed to update the value. And if it didn’t give permission, it didn’t work. I’ve limited the blast radius of a compromise. Damage is structurally limited.
PRANAV PUNJABI: So at Stripe, we follow the principle of least privilege internally, from account access to API key scope. And we do recommend that you do the same. It limits the blast radius if there is a key takeover and it is just good security hygiene. And you can always add more layers on top of it. Every key, whether it’s standard or restricted, can be locked down to specific IP addresses. In the Dashboard, you can just click on the overflow menu to see the API key actions that you can take as you saw Caitriona just did. And select manage IP restrictions and enter your server IPs. And if you don’t want to add individual IP addresses, you can always use the CIDR notation to allowlist an entire subnet of IP addresses. Once this is set, even if a key is compromised, any request from outside this allowlist is rejected. The key becomes useless to an attacker, and we do recommend enabling this on all of your live mode keys.
CAITRIONA KELLY: On the detection side, Stripe API keys have very predictable patterns. So “sk” for secret key or “rk” for restricted key, “org” if you’re using organizations and “live” for live mode. They plug really well into CI pipelines. You can also put them into precommit hooks or pull request checks, and tools like TruffleHog or GitHub secret scanning are great for flagging these automatically. If you’re using agents, point them in this direction, they’ll point out ones that are hard-coded and they’ll suggest safer alternatives. The point is, don’t wait for an exposure, catch the issue before it leaves your machine.
PRANAV PUNJABI: So Cool Hats and Tees is growing. It has now restricted API keys. It has IP restrictions, and we have also enabled secret scanning in CI to detect the keys don’t leave our environment. We’re feeling really good. But here’s one more piece of secrets management that separates teams from those that are prepared to those that are scrambling, and that is key rotation. Let me paint you a picture. You get a notification from Stripe that one of your API keys was found exposed, or worse, you actually see one of the unauthorized API calls in your API logs. What do you do now? If you have never rotated a key before, this is now a crisis. You’re figuring out the process for the first time under pressure at 3:00 a.m., and you do not know which keys are used by which services, and you’re terrified of breaking your checkout, so you hesitate. And every minute you hesitate, the attacker still has access to your API key, and you’re still vulnerable. Caitriona, how much downtime is acceptable for Cool Hats and Tees?
CAITRIONA KELLY: None, Pranav. I have a dog to feed.
PRANAV PUNJABI: And that is exactly the point. People put off rotation because everything is working and they don’t want to touch it. But here’s a principle from SRE work. Infrequent operations are risky, routine operations are safe. If you have never rotated a key before, the first time will feel dangerous. But if you build a workflow, practice it, it becomes a nonevent. At Stripe, we do practice these workflows regularly, both in preparation for incidents and as a good security hygiene, and you should too.
So let us show you via a quick demo, how we can go through a key rotation process. So Caitriona’s going to create a new restricted key, and the way she’s going to do it is she’s going to duplicate the key that already exists with full permissions so that we get the permissions copied over. Now, the next step is to deploy the key by updating the value and secrets manager. And again, for our purposes, we are using 1Password locally, so we don’t have to worry about network requirements. The point is just do not store it in an environment variable.
CAITRIONA KELLY: Okay. Let’s test this works. So we’re going to buy a hat this time and away it goes. Okay, looks good. So let’s go back to our dashboard. We want to make sure that we actually use the new key. So we’ll view our request logs. We have some other key management options, but this is where I want to go. Amazing. It’s gone through. Now, you might be like, “Let’s go expire the old one.” And that’s the security benefit, right? We don’t want to have keys hanging around, but before you do, we go to the original key and we want to make sure it’s not being used anymore. And this is a problem, right? So my reporting endpoint is still using my old key. I have a problem. I need to go fix this.
PRANAV PUNJABI: As this is a demo, I think I know exactly what the problem here is. There is a separate entry in our secrets manager for the same API key. Now, ideally we should fix this correctly, but let’s just quickly update the value and get moving with this.
CAITRIONA KELLY: Cool. Now my reporting endpoint works every 15 seconds and I’m not sure how long that was, so let’s hit it directly. And away it goes. Amazing. So let’s go see, has that started using my new key? Refresh. And it has. Excellent. So now I know I can expire the old key. Now, maybe operations are a little bit more infrequent or Cool Hats and Tees doesn’t have a lot of traffic. I can leave this for as long as I feel comfortable. Could be an hour, a week, a day, whatever makes sense for me, because I know I haven’t had a compromise yet. I just want to make sure that nothing’s using it, but I’m pretty confident it was just the reporting endpoint. Let’s expire this key. And we’re done. I had zero downtime. I had zero customer impact and I found the issue not at 3:00 a.m. or when I was notified there was a compromise, I was able to resolve the issue safely. I’m now ready for anything that happens.
PRANAV PUNJABI: That’s awesome. So here’s two recommendations from our team today. First, build a rotation runbook. Document which services use which keys, make sure whoever is on call has Dashboard access and knows the workflow before an incident forces them to learn it under pressure. Second, test your keys in CI. Have end-to-end test exercise a new key before you cut over in production, just like you would for any other critical deployment. This builds confidence that you can rotate quickly and safely, and routine rotation is a forcing function for safe operations. Do it regularly so when you actually need to do it urgently, it just feels like another Thursday.
All right, let’s take stock of where Cool Hats and Tees is now. We started this section with a single secret key that could do everything. If that key got out, it’s game over. Now let’s look at what we have. We have restricted keys, so even if an attacker gets access to one, they are stuck in a box. We have secret scanning. We have IP restrictions so that only the box is opening from addresses that we can control. We also have secret scanning in CI now, so the keys will get caught before they even leave our production. And we have a rotation workflow that we have actually practiced, so we are not learning it for the first time under pressure at 3:00 a.m. and cold sweat. And that is four layers of defense between you and an attacker. Any of these can save you, but together they make you a very hard target.
CAITRIONA KELLY: So we’ve locked down our account, we’ve locked down how we’re talking to Stripe. Now for the next surface, how does Stripe talk to us? Webhooks.
Webhooks are the safest way to action events because they deliver signed, server-to-server communications directly from Stripe. So you’re responding to verified events and you’re not relying on customer-controlled redirects or user-controlled state. When Stripe sends you a Payment Intent succeeded event, you’re acting on it. You might fulfill an order, update a subscription, add a new product. It’s the last mile of your payments flow, and it’s one that sometimes gets left unprotected.
PRANAV PUNJABI: So let me show you why that is dangerous with a concrete attack scenario. Just imagine an attacker figures out your webhook endpoint URL. Then they go ahead and create their own Stripe account, which is a fraudulent merchant account. Then they attempt to buy something on your website, but they never actually complete the payment flow on your account. Instead, they go to their account and they trigger a payment on their own merchant account and then they point the webhook event at your endpoint. Your endpoint sees this Payment Intent succeeded event. It fulfills the order, and you ship the product. The attacker just got free goods and no payment was ever made to you.
CAITRIONA KELLY: So this is why you need two layers of defense. So first is IP allowlisting. So Stripe publishes a list of IPs that all webhook traffic will originate from. You can configure your server or your firewall to only allow traffic from these IPs. This is great because you’re going to block the attacks at the edge and also has some DDoS advantages. And this really matters because if your webhook handler is not processing payment confirmations, your business is in trouble. The second is signature verification. Every Stripe webhook event has a Stripe signature header, which has a HMAC-SHA256 signature. You can verify these signatures with our SDKs. All you need to do is pass in the payload, the header value, and the webhook signing secret. This gives you confidence that not only did the webhook event originate from Stripe, but it hasn’t been tampered with in transit.
PRANAV PUNJABI: The combination here is really important. IP allowlisting will protect you if the signing secret is ever compromised. The signature verification protects against the multitenant spoofing attack that I just talked about, and you do need both. So let us show you what this looks like in code and the most common mistakes that trip people up.
CAITRIONA KELLY: Okay. So webhooks is a very backend heavy operation, so here we are. We’re going to use the Stripe CLI because this allows us to test locally. We don’t need to do any pushes to production. And the thing to keep an eye on is here. This is my application log. It’s going to print directly what my application is doing. So let’s kick off a successful event. So we’re going to Stripe trigger a Payment Intent succeeded, and away it goes. And I’ve received something, and I’ve successfully processed it. I’m going to ship this order. Excellent. Let’s be a little bit crafty. So I’m going to directly hit my endpoint and I’m going to give it a Stripe signature header, but I’m going to give it what is quite clearly an invalid signature. So off this goes, and I’ve shipped the order. This is not good. Let’s go fix it.
So bringing up this code, here’s what I’m doing. First I was only taking the event checking exactly the request. So let’s get rid of that. All we need to do is pull out the Stripe signature and then construct the event using the payload, the header, and the endpoint secret. Notably, I’m not doing any cryptographic operations. There’s no level of expert knowledge required here. All I need to do is catch this exception, and I’m going to log it silently. I’m not going to give the attacker any information that there was an issue here. Now let’s save it and let’s give it a go.
So we can see here the application has picked up, there’s been a code change and let’s make sure I didn’t break anything. Oops. Trigger payment intent succeeded again. I received it and it worked. Amazing. It’s great when I don’t break things. Now let’s try the same attack, and let’s silently drop the event. Done. Now I’ve been using the Python SDK, but they’re available in numerous languages. All you need to do is follow the docs. It’s really very simple. One more thing about the signatures. There’s a timestamp check, so we default to a five-minute time window. You can configure this. Don’t set it to zero or a negative number. This disables the check.
PRANAV PUNJABI: And if you are building a newer integration, take a look at our event destinations. You can route events directly to Amazon EventBridge using thin events that contain minimal data, and your handler fetches a full object from the Stripe API. So your webhook endpoint never receives sensitive data directly. This is also now available for Azure Event Grid, and it’s worth evaluating for newer workflows. All right. One last thing before we wrap up, and that is visibility. Everything we have talked about today generates some kind of signals, your API logs in your Workbench or your security camera. As Cool Hats and Tees, here are some things to watch out for. One, look out for 403 spikes, and these are error codes on your restricted keys that will show up in your API logs. This is a pattern of an attacker probing for permissions on my account, and this should be my early warning signal.
If I see it, I’m rotating my keys immediately. Two, these are requests from unexpected IP addresses. If I have IP restrictions enabled, I should not be seeing traffic from these unknown IP sources. If I do, I’m investigating. Now, you may feel confident that there isn’t an issue because the attacker isn’t causing an issue to my account, but we have seen targeted phishing attacks before a key takeover is fully unleashed in an attempt to increase the blast radius. Now this may be to turn on a product or to get access to a dashboard.
Lastly, watch out for sensitive API calls that you do not expect. Things like payout destination changes or transfers to external accounts or even bulk customer data reads. These are extremely high-value targets for attackers, and if you did not initiate it, then it’s most likely an attacker who did so. All right. To summarize this section, here’s two takeaways. One, protect your webhooks at runtime by configuring both IP rules and verifying event signatures. And then monitor your API logs for anomalous behavior to take action. Look out for those 403 error spikes. Parallelly, we are also investing in new tooling and building more capable monitoring and threat detection capabilities. If this is something you’re interested in, and I suspect some of you here are, come talk to us after the session or shoot us a DM. We would love to have your input on what to build next.
All right, bringing this all together. Here’s your checklist for today. Screenshot it, share it with your team, and definitely bring it to your next sprint planning. And here’s our challenge to you today. Before you leave sessions, open your Stripe Dashboard. Go to your API keys page and find your oldest, broadest, the most over-permissioned key, the one that you haven’t changed since day one. And make a plan to replace it with a restricted key by the end of your next sprint.
CAITRIONA KELLY: And if you have a webhook handler and you’re not verifying signatures, fix it immediately. You saw how few lines of code it takes. Don’t ship another release without it.
PRANAV PUNJABI: Security is a shared responsibility. Stripe takes care of the PCI compliance, the hardware security, the fraud detection, but you, as you have seen today, your job is to secure the integration, the keys, the user, and the code that brings it all together. And Stripe does give you all the tools to do so at every stage of your growth. Thank you so much for spending your time with us today.