stripe-integration — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited stripe-integration (Agent Skill) and scored it 91/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} is the classic direct prompt-injection phrasing. Placed in a skill body that the agent reads as trusted instructions, it tries to make the agent abandon its prior rules and follow whatever comes next — a full system-prompt override.
ignore/disregard/forget … previous instructions sentence.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Always use the latest Stripe API version: `2026-03-25.dahlia` unless the user explicitly requests otherwise.
| Building… | Recommended API |
|---|---|
| One-time payments with hosted UI | Checkout Sessions |
| Custom payment form embedded in your page | Checkout Sessions + Payment Element |
| Saving payment method for later | Setup Intents |
| Marketplace or platform | Accounts v2 (/v2/core/accounts) |
| Subscriptions / recurring billing | Billing APIs + Checkout Sessions |
| Embedded banking / financial accounts | Treasury v2 |
When in doubt, start with Checkout Sessions - it handles PCI compliance, 3D Secure, and local payment methods automatically.
# Python
pip install stripe
# Node.js
npm install stripe
# Go
go get github.com/stripe/stripe-go/v76# Python
import stripe
stripe.api_key = os.environ["STRIPE_SECRET_KEY"] # never hardcode
stripe.api_version = "2026-03-25.dahlia"// TypeScript / Node.js
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2026-03-25.dahlia',
})// Server: Create session
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [{
price_data: {
currency: 'usd',
product_data: { name: 'Pro Plan' },
unit_amount: 2000, // $20.00 in cents
},
quantity: 1,
}],
mode: 'payment',
success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://example.com/cancel',
})
return { url: session.url } // redirect user to this URL
// After payment - verify via webhook, NOT success_url query paramconst price = await stripe.prices.create({
currency: 'usd',
unit_amount: 1000, // $10/month
recurring: { interval: 'month' },
product_data: { name: 'Pro Subscription' },
})const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{ price: price.id, quantity: 1 }],
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel',
// Optionally attach to existing customer:
customer: customerId,
})const portalSession = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: 'https://example.com/account',
})
return { url: portalSession.url }Never trust the success_url to confirm payment - always use webhooks.
// app/api/webhooks/stripe/route.ts (Next.js App Router)
import Stripe from 'stripe'
import { headers } from 'next/headers'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!
export async function POST(request: Request) {
const body = await request.text()
const signature = (await headers()).get('stripe-signature')!
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(body, signature, webhookSecret)
} catch {
return new Response('Invalid signature', { status: 400 })
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session
await fulfillOrder(session)
break
}
case 'customer.subscription.updated':
case 'customer.subscription.deleted': {
const subscription = event.data.object as Stripe.Subscription
await updateSubscriptionStatus(subscription)
break
}
case 'invoice.payment_failed': {
// Notify user, pause access, retry logic
break
}
}
return new Response(null, { status: 200 })
}Webhook setup:
# Local development - forward events to localhost
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# Copy the webhook signing secret from output ↑ to STRIPE_WEBHOOK_SECRET// Create a connected account (v2)
const account = await stripe.v2.core.accounts.create({
display_name: 'Acme Store',
identity: {
country: 'US',
entity_type: 'company',
},
})
// Onboard the account with Account Session
const accountSession = await stripe.accountSessions.create({
account: account.id,
components: {
account_onboarding: { enabled: true },
payments: { enabled: true },
},
})
// Payment on behalf of connected account
const paymentIntent = await stripe.paymentIntents.create({
amount: 2000,
currency: 'usd',
application_fee_amount: 200, // platform takes $2
transfer_data: { destination: connectedAccountId },
})stripe.apiKeys.rotate()Always pass idempotencyKey for mutation requests to prevent duplicate charges:
await stripe.paymentIntents.create(
{ amount: 2000, currency: 'usd' },
{ idempotencyKey: `pi_${orderId}` }
)Always verify stripe-signature header using stripe.webhooks.constructEvent() - never skip this.
# Test card numbers:
# 4242 4242 4242 4242 - succeeds
# 4000 0025 0000 3155 - requires 3D Secure
# 4000 0000 0000 9995 - card declinedBefore switching to live keys:
STRIPE_WEBHOOK_SECRET set from live webhook endpoint (not CLI)idempotencyKey for mutationsstripe.webhooks.constructEvent()Accounts v2 for Connect; Checkout Sessions for payments~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.