Good Carder
Professional
- Messages
- 751
- Reaction score
- 493
- Points
- 63
Introduction: Why Payment APIs Are a Special Target
Payment APIs process trillions of dollars annually. A single vulnerability can lead to a leak of customer financial data, widespread fraud, or regulatory fines amounting to millions of euros. Payment gateway testing is fundamentally different from standard penetration testing — it examines not just web application vulnerabilities, but business logic specific to financial systems: handling race conditions in transactions, manipulating authentication tokens, and the security of multi-component payment processes.In this article, we examine the methodology for testing Stripe and Adyen payment gateways through the lens of the OWASP API Security Top 10, including race condition detection, webhook forgery, OAuth token interception, and the tools that make this work possible. All of the above applies exclusively to authorized penetration testing with the written permission of the system owner.
Part 1. OWASP API Security Top 10 for Stripe and Adyen
The OWASP API Security Top 10 2023 is the latest standard that identifies critical API risks, including improper object and function handling, authentication weaknesses, and unbounded resource consumption.1.1. API1:2023 – Broken Object Level Authorization (BOLA)
This is the most common and critical vulnerability in payment APIs. A hacker, by changing the object identifier in a request (for example, payment_intent_id or customer_id), can access data not belonging to their session.What to test in Stripe and Adyen:
- Ability to replace payment_method_id or customer_id in a payment creation request.
- Access webhooks of other merchants by changing the ID in the request.
- The ability to view other people's chargebacks or payout reports.
Tools: Burp Suite Intruder for substituting IDs into request parameters, automated brute-force testing using custom dictionaries. When testing BOLA, it's important to check not only direct IDs but also objects accessible through nested structures (e.g., payment_intent.charges.data[].customer).
1.2. API2:2023 – Broken Authentication
We're testing the entire authentication cycle: from receiving the client_secret to refreshing tokens and logging out.Key points in Stripe:
- The client_secret is used in the client-side (stripe.confirmCardPayment). Check if you can substitute the client_secret in the browser console to confirm someone else's payment.
- Refresh token race condition. Stripe maintains refresh token rotation with each refresh. In a multi-process, lock-free environment, it's possible for multiple processes to refresh the token simultaneously, resulting in a failure with the invalid_grant error because one process is using an already invalid refresh token.
When testing authentication, it's important to check how the system handles concurrent token refresh requests and whether an old refresh token is returned as valid after rotation.
OAuth 2.0 in payment systems: Stripe uses OAuth to connect platforms; Adyen uses it for secure webhooks. Test:
- Possibility of intercepting and reusing the authorization code.
- Availability of CSRF protection in OAuth endpoints.
- Correctness of redirect checking (open redirects in callback URI).
1.3. API3:2023 – Broken Object Property Level Authorization (BOPLA)
A hacker gains access to an object (for example, their payment intent), but can manipulate properties they are not authorized to change.What to test:
- Ability to replace statement_descriptor and receipt_email in PaymentIntent.
- Changing the description or metadata of someone else's object.
- Access to the card_present or payment_method_details fields, which typically reveal sensitive information but can be accessed through undocumented endpoints. In the Adyen API, check the additionalData parameter—under certain conditions, it can alter the payment processing (for example, by applying an unauthorized penalty).
1.4. API5:2023 – Broken Function Level Authorization
What to test:- Using administrative functions (e.g. v1/accounts/{id}/reject in Stripe Connect) from a regular account.
- Accessing undo/revert endpoints by substituting someone else's transaction ID.
- Calling internal-only functions (e.g. v1/radar) from outside.
1.5. API8:2023 – Security Misconfiguration
Configuration errors are among the most common and easily exploited vulnerabilities.What to check:
- Are the /v1/setup_intents or /v1/payment_methods endpoints accessible without permissions checks in Stripe?
- Are unused payment methods (e.g. undisabled test gateways) activated in Adyen?
- Whether detailed error information is present in API responses (stack trace and internal paths) is a sign of a debug configuration that is not intended for production.
- Does webhook signature verification work with empty secret in Stripe (CVE-2026-41432)?
1.6. PSD2 и Open Banking Compliance
PSD2 compliance testing is critical for pentesting payment systems in Europe. Key requirements:- Strong Customer Authentication (SCA) is validated on all transaction types and requires at least two independent authentication factors.
- Dynamic Linking ensures that the one-time code is linked to a specific transaction amount and payment recipient. Test the ability to change the amount after SCA.
- OAuth 2.0 and mTLS for Open Banking. Test certificate validation (check whether the API accepts an expired self-signed certificate), prevent privilege escalation (a read-only token should not initiate payments), and protect against authorization code interception.
1.7. Adyen Implementation Vulnerabilities
When testing Adyen, the following are important:- HMAC signature of webhooks based on SHA256 (mandatory verification).
- OAuth 2.0 endpoints for secure notification delivery — check whether the authorization header can be spoofed and access other users' events.
Part 2: Finding Race Conditions in Transaction Processing
A race condition is one of the most dangerous vulnerabilities in payment systems. It occurs when two parallel processes simultaneously access a shared resource, and the outcome depends on which process completes first. In the context of payments, a race condition can lead to double crediting, double card charges, or the provision of goods or services without actual payment.2.1. Race condition with simultaneous payment confirmation
A typical scenario: a web application creates a PaymentIntent in Stripe and passes the client_secret to the client. The client calls stripe.confirmCardPayment, and Stripe confirms the payment. After receiving the payment_intent.succeeded webhook, the server credits the user. The issue arises when the same client_secret is used in two concurrent requests to Stripe. One request may charge funds, while the second may fail, but both may be interpreted as successful by the server if the webhook code doesn't handle the status properly.Testing technique:
- Intercept the payment confirmation request in Burp Suite (Repeater).
- Send two identical requests as quickly as possible (the delay between requests should not exceed 5-10 ms). For automation, you can write a Python script using threads.
- Observe if two successful transactions are created (two successful payment_intents with different ids).
- If they are created, check whether a hacker could use this to obtain a product or service without payment within a single legitimate transaction.
Stripe is generally well-protected against race conditions thanks to its idempotency, but vulnerabilities can occur in custom webhook processing code, especially if the handler is not idempotent.
A real-world example: In one project, Stripe's webhook processing code performed a synchronous write to the database within the handler. Under high load (waiting for more than 30 seconds), Stripe resent the webhook, which did not receive a 200 OK response in time. As a result, the same transaction was processed twice, creating a double credit. The issue was fixed by switching to an asynchronous queue: the webhook only stores the event, and a separate worker processes it later.
2.2. Race condition when processing idempotency keys
Idempotency is when re-sending the same request with the same Idempotency Key does not result in a double charge. Stripe guarantees that the first request with a given key will be processed, and subsequent requests with the same key will return the same result without double charges.What to test: send two identical payment requests with the same Idempotency Key simultaneously. Check whether two paid PaymentIntents are created. If so, this indicates a race condition in the idempotency implementation. Stripe uses a locking mechanism to prevent such scenarios, but issues can arise at the integration level when two servers generate identical keys.
2.3. Race condition when balancing the balance
Many payment systems hold funds in a client's virtual account before crediting them to the merchant's real account. If a hacker simultaneously sends two debit requests, the system may allow both, even if the balance is insufficient.How to test: Create an account with a minimum balance (e.g., 10). Simultaneously send two debit requests of 7 (this is only possible using different Idempotency Keys). If both debits are successful, the balance has become negative (10 - 7 - 7 = -4), but the platform may allow the transaction, resulting in unlimited withdrawals.
Part 3. Faking Webhook Events and Authentication
Webhooks are the Achilles heel of many payment integrations. A hacker, by faking a successful webhook, can trick the system into crediting funds or activating paid access without actual payment.3.1. Stripe Webhook Signature Attacks
Stripe signs each webhook with a secret key (whsec_*), which must be stored on the merchant's server. By default, Stripe requires that the StripeWebhookSecret value be set in code, and it must not be empty.Critical vulnerability of 2026 (CVE-2026-41432): If StripeWebhookSecret is left empty (the default value), the HMAC signature is calculated with an empty key. Any hacker can forge a webhook with a valid signature generated by the same algorithm with an empty key. Combined with the lack of a check for payment_status == "paid," an attacker can fake a successful checkout.session.completed event to top up their internal account without actually paying. The vulnerability was fixed in version 0.12.10.
How to test as part of a sanctioned penetration test:
- Please check if the webhook secret is configured in your Stripe environment (it should not be empty).
- Check if your handler code has a signature check using the Stripe::Webhook.construct_event library.
- Please check if event.type == "checkout.session.completed" and event.data.object.payment_status == "paid" are checked before funds are credited.
- Using stripe listen --forward-to localhost:3000/webhooks, intercept the legitimate webhook and try changing its parameters (amount, status). The server should reject the fake webhook due to an invalid signature.
It's also important to check how checkout.session.async_payment_succeeded and checkout.session.async_payment_failed are handled for delayed payments (e.g., bank transfers). If they aren't handled, funds may be credited and then later revoked.
3.2. Adyen webhook attacks
Adyen supports SHA256 HMAC signatures and OAuth 2.0 for webhooks authentication. For Standard Webhooks, OAuth 2.0 is recommended, and the HMAC signature must be verified regardless of the authentication method. It's important to verify that webhooks are authenticated using either HMAC or OAuth, and that the system doesn't accept requests without valid authorization.What to test:
- Whether the endpoint uses proper HMAC validation (including domain and IP validation).
- Are timeouts (10 seconds) handled correctly? Does a long response result in resending and duplicating events?
- Is resending configured for notifications that have remained in the queue for more than 30 days?
- Does the event filtering feature work to prevent a hacker from receiving unwanted notifications?
3.3 Cross-gateway exploitation (Adyen and Stripe)
CVE-2024-41432 was missing a validation for the PaymentMethod field. An order created via Epay (or another unsupported gateway) could be fulfilled using a fake Stripe webhook.How to test it as part of a sanctioned penetration test:
- Create an order using the Epay or Cash payment method.
- Send a fake checkout.session.completed webhook to your app's Stripe endpoint.
- If the order is completed and funds are credited, the application is vulnerable to cross-gateway exploitation.
Part 4. Intercepting OAuth tokens and authenticating payments
OAuth 2.0 is widely used by payment systems to authorize third-party applications, access APIs, and securely deliver webhooks. A leaked authentication token could lead to complete takeover of a merchant's account.4.1. Main attack vectors on OAuth
Authorization code interception occurs when a hacker intercepts an authorization code as it travels from a provider (Stripe, Adyen) to an application. Security techniques include:- Using PKCE (Proof Key for Code Exchange).
- Storing code_challenge and code_verifier in the session with CSRF protection.
- Binding the callback URI to a valid domain (open redirect). Stripe and Adyen require an exact match — this is an important check.
CSRF in OAuth endpoints occurs when a hacker creates a fake authorization request on behalf of the victim. Protection is provided by the state parameter (a unique string associated with the session). When testing, check for missing state validation in the callback endpoint.
Leaking tokens from logs and sniffing is another common scenario. All requests containing tokens should only be sent over TLS 1.2+ and should not be logged in cleartext.
4.2 Token Interception in Stripe and Adyen
Stripe OAuth:- Tokens (access_token and refresh_token) are used to access the Stripe API on behalf of the connected account.
- A refresh token is valid for one year, but when refreshed, Stripe rotates the tokens: both tokens are replaced with a new pair, and the old refresh token becomes invalid.
Race condition when refreshing tokens: If two servers simultaneously attempt to refresh a token, an invalid_grant error may occur because one process is using an invalid refresh token. This is not a Stripe vulnerability, but an integration error that causes crashes.
Adyen OAuth 2.0: Adyen uses OAuth tokens to securely send webhooks. The token is sent in the Authorization header of each webhook. Ensure that your webhook endoint verifies the validity of the Authorization header and does not accept fake requests without it.
What to test as part of a sanctioned penetration test:
- Token leak in client code (.js files).
- Storing refresh token in logs.
- Transfer of tokens through open channels.
- Correctness of token revocation when changing a password or revoking access.
Part 5. Testing Toolkit: From Burp Suite to Custom Fuzzers
5.1. Burp Suite Pro — foundation of pentesting
Burp Suite Pro intercepts, analyzes, and modifies HTTP/HTTPS/HTTP2 requests, including API calls from Stripe and Adyen. All requests can be easily modified in the proxy. Key features:Repeater — Manually modify and resend requests. Used to test BOLA and race conditions with a delay between requests.
Intruder — Automates substitution attacks. For BOLA testing, Intruder substitutes different payment_intent_ids and analyzes responses.
Scanner (Pro) — Automatically scans API endpoints, including checking for SQL injections, XSS, and confidential data leaks.
Extensions — Extensions such as API Sniffer automatically find API keys in proxied traffic and add them to Issues.
Since 2021, Burp Suite Professional and Enterprise Editions have included the "API-only scan" feature, which allows you to download API specifications (OpenAPI, SOAP WSDL) for automatic scanning.
5.2. Postman for Functional Testing
Postman is a powerful tool for manual and automated API testing:- Import official Stripe and Adyen API collections.
- Use collection variables (e.g. {{stripe_secret_key}}) to store keys.
- Write JavaScript tests to check response statuses and data schemas.
- Use Pre-request Scripts to dynamically create non-identical requests (e.g. different Idempotency-Key for each request).
Workflow: Import the Stripe and Adyen OpenAPI specifications into Postman → create an environment with test keys → write tests for each endpoint you want to test. Postman is especially useful for testing authentication and detecting BOLA errors — you can quickly replace payment_intent_id in the request and observe the response.
5.3. Custom Fuzzer in Python
When ready-made tools aren't enough, Python is your best friend. curl_cffi simulates browser TLS fingerprinting, and aiohttp processes thousands of requests asynchronously.Basic fuzzer for testing race condition when confirming a payment:
Python:
import asyncio
import aiohttp
import time
async def confirm_payment(session, payment_intent_id, client_secret):
url = f" [URL]https://api.stripe.com/v1/payment_intents/[/URL] {payment_intent_id}/confirm"
headers = {"Authorization": "Bearer sk_test_..."}
data = {"client_secret": client_secret}
async with session.post(url, headers=headers, data=data) as response:
return await response.json()
async def race_condition_test():
async with aiohttp.ClientSession() as session:
tasks = [confirm_payment(session, "pi_123", "secret_123") for _ in range(5)]
start = time.time()
results = await asyncio.gather(*tasks)
duration = time.time() - start
print(f"Completed {len(results)} requests in {duration:.2f} seconds")
print(f"Unique payment_intent IDs: {set(r.get('id') for r in results)}")
asyncio.run(race_condition_test())
Fuzzer for testing the signature of Stripe webhooks (CVE-2024-41432):
Python:
import hmac
import hashlib
import json
import requests
def forge_stripe_webhook(webhook_url, amount):
payload = {
"id": "evt_fake123",
"type": "checkout.session.completed",
"data": {"object": {"id": "cs_fake", "amount_total": amount, "payment_status": "paid"}}
}
body = json.dumps(payload)
secret = ""
signature = hmac.new(secret.encode(), body.encode(), hashlib.sha256).hexdigest()
headers = {"Stripe-Signature": f"t={int(time.time())},v1={signature}"}
response = requests.post(webhook_url, data=body, headers=headers)
print(response.status_code, response.text)
5.4 Local testing of webhooks
To safely test webhooks in an isolated environment, use:- Stripe CLI - stripe listen --forward-to localhost:3000/webhooks forwards real Stripe events to the local development server.
- Webhook Relay or Hookdeck — tunneling local webhooks to the internet for testing with Stripe.
- Hookdeck CLI - hookdeck listen 3000 stripe creates a public URL that redirects to a local webhook endpoint.
- Adyen Test Webhooks — The Adyen control panel allows you to manually send test webhooks (AUTHORISATION, CAPTURE, REFUND) to check the logic.
5.5. Standards Testing Tools (2026)
- OWASP API Security — collection of resources for developers, including testing frameworks, libraries, articles, and practices for secure API design.
- PCI DSS v4.0 — requires annual penetration testing of systems that process cardholder data (requirements 11.3.1, 11.3.2, 11.3.4).
- DORA (EU) — includes explicit requirements for Threat-Led Penetration Testing from 2025. DORA also implements the TIBER-EU standard.
- EBA ICT Risk Guidelines — set out pentesting requirements for financial institutions.
- ISO 27001 — Recommends regular security testing and API review.
5.6. Integrating Testing into CI/CD (Shift Left)
For modern security teams, integrating API pentesting into the CI/CD pipeline is critical. Use Postman CLI or Newman to automatically run API tests with each deployment. Burp Suite Professional supports command-line API scanners that can be easily integrated into Jenkins.Part 6. Legal Framework: Permission, Bug Bounty, and Compliance
6.1. Testing Permission
Penetrating a payment gateway without the system owner's express permission is a computer crime. Requirements are documented through:- Letter of Authorization — signed document from the target owner stating the scope, period, and purpose of testing.
- Rule of Engagement (RoE) — defines testing methods (whether social engineering is allowed), IP ranges, and escalation procedures.
- Nondisclosure Agreement (NDA) — obligation not to disclose information discovered during a test.
OWASP WSTG guidelines emphasize the need to obtain written permission before scanning, intercepting traffic, or conducting automated attacks.
6.2. Legal requirements for pentesting (2026)
Regulators around the world impose strict requirements for penetration testing of financial systems. Many of these requirements are becoming mandatory over time:- PCI DSS v4.0 explicitly requires pentesting for systems processing cardholder data. Requirement 11.3 (Sections 11.3.1 and 11.3.2) requires annual testing and testing after significant changes. Requirement 11.3.4 specifically addresses web applications to check segmentation and identify vulnerabilities such as SQL injection and XSS.
- The GLBA Safeguards Rule (USA) requires financial institutions to conduct annual penetration testing or continuous monitoring to protect NPI. This is an obligation, not a recommendation.
- GDPR requires "appropriate technical and organizational measures." Although pentesting isn't explicitly mentioned, Recital 49 and DPIAs imply testing for high-risk web applications.
- PSD2 (Europe) - Mandates SCA and dynamic binding for payment APIs.
- DORA (EU) - Threat-Driven Penetration Testing (TLPT) is mandatory for large financial institutions from January 2025. DORA requires threat model-based testing and implements the TIBER-EU standard.
6.3. Bug Bounty Programs for Payment Gateways
Major payment processors encourage ethical vulnerability discovery through official bug bounty programs, many of which are managed by HackerOne platforms.Stripe operates its own bug bounty program. The confidential nature of their internal infrastructure means details are typically not disclosed, but rewards up to $20,000 for critical vulnerabilities (RCE, SQLi, XSS).
Adyen publishes its responsible disclosure policy but does not operate a public bug bounty program. They welcome reports of security issues through their official channel, but payouts are limited at Adyen's discretion. Disclosure must be done responsibly.
Participation rules:
- Maintain volume. Stripe and Adyen have strict bug bounty policies that protect live users. Testing payment forms or webhooks without permission is prohibited.
- Use test environments. Stripe provides test keys and card numbers (e.g., 4242 4242 4242 4242 with any CVV/expiration date information), while Adyen provides test accounts that don't charge real funds.
- Do not test live customer accounts. All tests are conducted on controlled merchant accounts.
- Please report any findings through the official channel. Stripe requires you to submit your report through HackerOne for review.
- Expect scoring. Stripe evaluates vulnerabilities based on CVSS v3.1, including authentication requirements and potential damage.
Part 7. Final checklist for a white-hat payment gateway pentest
Preparation and volume:- A written permission (Letter of Authorization) was received with a clear definition of the scope and period of testing.
- Test merchant accounts have been created in Stripe and Adyen.
- A local development environment has been set up that replicates the payment gateway integration (using Stripe CLI to send webhooks).
- API endpoints have been defined for the following areas: payment creation, webhook notifications, OAuth flows, and administrative functions.
API and business logic testing:
- Burp Suite is running to intercept and modify all requests to the Stripe/Adyen API.
- BOLA testing has been performed: all object IDs (customer, payment_intent, subscription) have been replaced.
- Function-level authorization (BFLS) has been verified: administrative functions are called from a standard account.
- Race condition testing completed: parallel payment confirmation requests sent using Intruder/custom fuzzer.
- Idempotency implementation has been verified: identical Idempotency-Keys were sent at close times.
- OAuth 2.0 tested: CSRF protection using the state parameter and secure storage of tokens without leaks in logs.
- PSD2 compliance verified: SCA, dynamic binding, and mTLS testing for Open Banking API.
Testing webhooks:
- Stripe webhook signature verified: handler rejects requests with invalid signature; StripeWebhookSecret is not empty.
- Checked that the checkout.session.completed event is processed only when payment_status == "paid".
- Checked handling of deferred events (async_payment_succeeded, async_payment_failed), if they are relevant.
- Cross-gateway exploitation testing completed: fake Stripe webhook for an order created through a different gateway.
- The HMAC signature of Adyen webhooks has been verified using OAuth 2.0 for authentication.
- Webhook endpoints have been tested for DoS vulnerability (sending multiple requests to trigger Stripe resends).
Tools and automation:
- Used Burp Suite Professional for testing BOLA (Intruder) and Repeater for manual analysis.
- The OpenAPI specification has been imported into Postman; automated tests have been written for all critical flows.
- A custom Python fuzzer was written for testing race conditions, faking webhooks, and parameter iteration.
- Webhooks were tested using Stripe CLI and Hookdeck.
Reporting and remediation:
- A report has been prepared detailing each vulnerability with evidence (screenshots, logs, curl commands).
- The vulnerabilities are classified according to the CVSS v3.1 standard; the criticality is assessed for each.
- Reproducible steps to fix the issue are provided (with clear code instructions).
- Disclosure information is provided to a limited circle: only to the person authorized for remediation.
- All test data is cleared after the pentest is completed.
Conclusion: Mastery and Responsibility
Penetrating a payment gateway is a test of technical skill, ethical discipline, and legal literacy. One untested endpoint or misconfigured webhook can lead to catastrophic financial losses.The three key takeaways from this article are:
- Payment APIs require a special approach to testing. Standard scanners are not enough. PSD2/Open Banking compliance testing, race condition detection in payment flows, and webhook communication security are all necessary.
- Automation speeds up, but doesn't replace, human expertise. Burp Suite, Postman, and Python can send thousands of requests, but only a human can interpret the results and understand the business context of a vulnerability.
- Permission isn't a formality. It protects both the pentester and the target owner from legal consequences. Working within the bug bounty program simplifies the process and allows for responsible disclosure.
A competent security professional knows that mastery isn't just about hacking, it's about doing it responsibly.
A quick one-liner:
"Payment APIs are vulnerable to BOLA, race conditions, webhook spoofing, and OAuth token leaks. Burp Suite for interception, Python for fuzzing, Postman for automation. Responsibility is just as important as mastery."
