StripeKit
A Swift package for communicating with the Stripe API in Server Side Swift applications.
Stripe API Version
2026-05-27.dahlia
Requirements
- Swift 6.2+
- Linux, macOS 12+ / iOS 15+ / tvOS 15+ / watchOS 8+
Installation
Add StripeKit to your Package.swift:
.package(url: "https://github.com/vapor-community/stripe-kit.git", from: "22.0.0")
Then add it as a target dependency:
.target(name: "MyTarget", dependencies: [
.product(name: "StripeKit", package: "stripe-kit")
])
Getting Started
Initialize a StripeClient with an HTTPClient and your Stripe API key:
import AsyncHTTPClient
import StripeKit
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
let stripe = StripeClient(httpClient: httpClient, apiKey: "sk_live_...")
Each API is accessed as a property on the client:
let charge = try await stripe.charges.create(
amount: 2500,
currency: .usd,
description: "A server written in Swift.",
source: "tok_visa"
)
if charge.status == .succeeded {
print("Payment succeeded 🎉")
}
Expandable Objects
StripeKit supports expandable objects via three property wrappers: @Expandable, @DynamicExpandable, and @ExpandableCollection.
All API routes that can return expanded objects accept an expand: [String]? parameter.
@Expandable
// Expand a single field
let paymentIntent = try await stripe.paymentIntents.create(
amount: 2500, currency: .usd, expand: ["customer"]
)
paymentIntent.$customer?.email // "user@example.com"
// Expand multiple fields
let paymentIntent = try await stripe.paymentIntents.create(
amount: 2500, currency: .usd, expand: ["customer", "payment_method"]
)
paymentIntent.$customer?.email // "user@example.com"
paymentIntent.$paymentMethod?.card?.last4 // "4242"
// Expand nested fields
let paymentIntent = try await stripe.paymentIntents.create(
amount: 2500, currency: .usd, expand: ["payment_method.customer"]
)
paymentIntent.$paymentMethod?.card?.last4 // "4242"
paymentIntent.$paymentMethod?.$customer?.email // "user@example.com"
Note: For list operations, expanded fields must start with
data:let list = try await stripe.paymentIntents.listAll(filter: ["expand[]": "data.customer"]) list.data?.first?.$customer?.email // "user@example.com"
@DynamicExpandable
Some objects can expand into one of several types. For example, ApplicationFee.originatingTransaction can be either a Charge or a Transfer:
let fee = try await stripe.applicationFees.retrieve(
fee: "fee_1234", expand: ["originating_transaction"]
)
fee.$originatingTransaction(as: Charge.self)?.amount // 2500
fee.$originatingTransaction(as: Transfer.self)?.destination // "acct_..."
@ExpandableCollection
let invoice = try await stripe.invoices.retrieve(invoice: "in_12345", expand: ["discounts"])
invoice.discounts // ["di_1", "di_2", ...] (String IDs)
invoice.$discounts?[0].id // "di_1" (expanded Discount objects)
Custom Headers
Use the builder-style addHeaders(_:) API to attach per-request headers such as Stripe-Account for Connect or Idempotency-Key:
// Connected account
let charge = try await stripe.charges
.addHeaders(["Stripe-Account": "acct_12345"])
.create(amount: 2500, currency: .usd, source: "tok_visa")
// Idempotency key
let refund = try await stripe.refunds
.addHeaders(["Idempotency-Key": UUID().uuidString])
.create(charge: "ch_12345", reason: .requestedByCustomer)
Note: Modified headers persist on the route instance for the lifetime of the reference. When accessing the
StripeClientinside a request scope (e.g. a Vapor route handler), headers are not retained between requests.
Webhooks
Verify and decode Stripe webhook events:
func handleStripeWebhook(req: Request) async throws -> Response {
let signature = req.headers.first(name: "Stripe-Signature") ?? ""
try StripeClient.verifySignature(
payload: Data(req.body.readableBytesView),
header: signature,
secret: "whsec_..."
)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
decoder.keyDecodingStrategy = .convertFromSnakeCase
let event = try decoder.decode(Event.self, from: Data(req.body.readableBytesView))
switch (event.type, event.data?.object) {
case (.paymentIntentSucceeded, .paymentIntent(let paymentIntent)):
print("Payment succeeded: \(paymentIntent.id)")
return Response(status: .ok)
default:
return Response(status: .ok)
}
}
Using with Vapor
import Vapor
import StripeKit
extension Application {
public var stripe: StripeClient {
guard let key = Environment.get("STRIPE_API_KEY") else {
fatalError("STRIPE_API_KEY environment variable is required")
}
return StripeClient(httpClient: self.http.client.shared, apiKey: key)
}
}
extension Request {
private struct StripeKey: StorageKey {
typealias Value = StripeClient
}
public var stripe: StripeClient {
if let existing = application.storage[StripeKey.self] {
return existing
}
guard let key = Environment.get("STRIPE_API_KEY") else {
fatalError("STRIPE_API_KEY environment variable is required")
}
let client = StripeClient(httpClient: application.http.client.shared, apiKey: key)
application.storage[StripeKey.self] = client
return client
}
}
Webhook Signature Verification with Vapor
extension StripeClient {
/// Verifies a Stripe webhook signature from a Vapor `Request`.
/// - Parameters:
/// - req: The incoming `Request`.
/// - secret: The webhook endpoint secret (`whsec_...`).
/// - tolerance: Maximum age of the timestamp in seconds (default: 300).
public static func verifySignature(
for req: Request,
secret: String,
tolerance: Double = 300
) throws {
guard let header = req.headers.first(name: "Stripe-Signature") else {
throw StripeSignatureError.unableToParseHeader
}
guard let body = req.body.data else {
throw StripeSignatureError.noMatchingSignatureFound
}
try StripeClient.verifySignature(
payload: Data(body.readableBytesView),
header: header,
secret: secret,
tolerance: tolerance
)
}
}
extension StripeSignatureError: AbortError {
public var reason: String {
switch self {
case .unableToParseHeader: return "Unable to parse Stripe-Signature header"
case .noMatchingSignatureFound: return "No matching signature was found"
case .timestampNotTolerated: return "Timestamp was not tolerated"
}
}
public var status: HTTPResponseStatus { .badRequest }
}
Implemented APIs
Core Resources
- Balance
- Balance Transactions
- Charges
- Customers
- Customer Sessions
- Disputes
- Events
- Files
- File Links
- Mandates
- Payment Intents
- Setup Intents
- Setup Attempts
- Payouts
- Refunds
- Confirmation Tokens
- Tokens
- Ephemeral Keys
Payment Methods
- Payment Methods
- Payment Method Configurations
- Payment Method Domains
- Bank Accounts
- Cash Balance
- Cash Balance Transactions
- Cards
- Sources
Products
- Products
- Prices
- Coupons
- Promotion Codes
- Discounts
- Tax Codes
- Tax Rates
- Shipping Rates
Checkout
- Sessions
Payment Links
- Payment Links
Billing
- Alerts
- Credit Notes
- Credit Grants
- Credit Balance Summary
- Credit Balance Transactions
- Customer Balance Transactions
- Customer Portal
- Customer Tax IDs
- Invoices
- Invoice Items
- Invoice Rendering Templates
- Meters
- Meter Events
- Meter Event Adjustments
- Plans
- Quotes
- Quote Line Items
- Subscriptions
- Subscription Items
- Subscription Schedule
- Test Clocks
- Usage Records
Entitlements
- Features
- Product Features
- Active Entitlements
Connect
- Account
- Account Login Links
- Account Links
- Account Sessions
- Application Fees
- Application Fee Refunds
- Capabilities
- Country Specs
- External Accounts
- Persons
- Top-ups
- Transfers
- Transfer Reversals
- Secret Management
Fraud
- Early Fraud Warnings
- Reviews
- Value Lists
- Value List Items
Issuing
- Authorizations
- Cardholders
- Cards
- Disputes
- Funding Instructions
- Transactions
Terminal
- Connection Tokens
- Locations
- Readers
- Hardware Orders
- Hardware Products
- Hardware SKUs
- Hardware Shipping Methods
- Configurations
Sigma
- Scheduled Queries
Reporting
- Report Runs
- Report Types
Identity
- Verification Sessions
- Verification Reports
Webhooks
- Webhook Endpoints
- Signature Verification
License
StripeKit is available under the MIT license. See LICENSE for details.