| Internet-Draft | Stripe Charge | March 2026 |
| Ryan & Kaliski | Expires 4 September 2026 | [Page] |
This document defines the "charge" intent for the Stripe payment method within the Payment HTTP Authentication Scheme [I-D.httpauth-payment]. It specifies how clients and servers exchange one-time payments using Stripe Payment Tokens (SPTs).¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 4 September 2026.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
This specification defines the "charge" intent for use with the Stripe payment method in the Payment HTTP Authentication Scheme [I-D.httpauth-payment]. The charge intent enables one-time payments where the server processes the payment immediately upon receiving a Stripe Payment Token (SPT).¶
Stripe provides payment processing through SPTs, which are single-use tokens that represent payment authorization. SPTs abstract away the complexity of payment method details (cards, bank accounts, wallets) and provide a unified interface for payment acceptance.¶
The following diagram illustrates the Stripe charge payment flow:¶
Client Server Stripe
| | |
| (1) GET /resource | |
|----------------------------> | |
| | |
| (2) 402 Payment Required | |
| intent="charge", | |
| request=<base64url> | |
|<----------------------------- | |
| | |
| (3) Collect payment method | |
| via Stripe.js and | |
| generate SPT | |
| (may prompt for 3DS, | |
| biometrics, etc.) | |
|------------------------------------------------------------> |
| | |
| (4) Authorization: | |
| Payment <credential> | |
|----------------------------> | |
| | (5) Create PaymentIntent |
| | (Stripe API, using SPT) |
| |----------------------------> |
| | |
| (6) 200 OK | |
| Payment-Receipt: | |
| <receipt> | |
|<---------------------------- | |
| | |
¶
This document is a payment method intent specification as defined in
Section 10.1 of [I-D.httpauth-payment]. It defines the request and
payload structures for the charge intent of the stripe payment
method, along with verification and settlement procedures.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
A single-use token (prefixed with spt_) that represents authorization
to charge a payment method. SPTs are created by clients using the
Stripe API and consumed by servers to process payments. Both the Client
and Server require a Stripe account. In the Stripe API, SPTs are
referenced as shared_payment_granted_token on PaymentIntent creation.
Learn more: https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens¶
A Stripe profile is a business’s public identity on Stripe. With a Stripe profile, businesses can find, verify, and connect with each other on Stripe. Learn more: https://docs.stripe.com/get-started/account/profile¶
A Stripe API object that tracks the lifecycle of a customer payment, from creation through settlement. Not to be confused with the HTTP Payment Auth protocol's "payment intent" parameter. Learn more: https://docs.stripe.com/payments/payment-intents¶
This specification defines the following intent for the stripe payment
method:¶
charge¶
The intent identifier is case-sensitive and MUST be lowercase.¶
A one-time payment of the specified amount. The server processes the payment immediately upon receiving the SPT.¶
Fulfillment mechanism:¶
Stripe Payment Token (SPT): The payer creates an SPT using the Stripe API, which the server uses to create a PaymentIntent via Stripe.¶
The request parameter in the WWW-Authenticate challenge contains a
base64url-encoded JSON object with the following fields. The JSON MUST
be serialized using JSON Canonicalization Scheme (JCS) [RFC8785] before
base64url encoding, per Section 5.1.2 of [I-D.httpauth-payment].¶
| Field | Type | Required | Description |
|---|---|---|---|
methodDetails.networkId
|
string | REQUIRED | Stripe Business Network Profile ID |
methodDetails.paymentMethodTypes
|
[]string | REQUIRED | The list of payment method types that the seller can process. |
methodDetails.metadata
|
object | OPTIONAL | Key-value pairs for additional context |
Example:¶
{
"amount": "5000",
"currency": "usd",
"description": "Premium API access for 1 month",
"externalId": "order_12345",
"methodDetails": {
"networkId": "profile_1MqDcVKA5fEO2tZvKQm9g8Yj",
"paymentMethodTypes": ["card", "link"]
}
}
¶
The client fulfills this by creating an SPT using Stripe:¶
const spt = await stripe.sharedPayment.issuedTokens.create({
payment_method: 'pm_123',
usage_limits: {
currency: 'usd',
max_amount: 5000,
expires_at: Timestamp
},
seller_details: {
networkId: 'profile_123'
}
});
// Returns: { id: 'spt_1N...' }
¶
The Payment credential is a base64url-encoded JSON object containing
challenge and payload fields per Section 5.2 of
[I-D.httpauth-payment]. For Stripe charge, the payload object
contains the following fields:¶
| Field | Type | Required | Description |
|---|---|---|---|
spt
|
string | REQUIRED | Stripe Payment Token ID (starts with spt_) |
externalId
|
string | OPTIONAL | Client's reference ID |
Example:¶
{
"spt": "spt_1N4Zv32eZvKYlo2CPhVPkJlW",
"externalId": "client_order_789"
}
¶
Servers MUST verify Payment credentials for charge intent:¶
Synchronous settlement:¶
Server creates a Stripe PaymentIntent with confirm: true and the
SPT as shared_payment_granted_token:¶
const paymentIntent = await stripe.paymentIntents.create({
amount: Number(request.amount),
currency: request.currency,
shared_payment_granted_token: credential.spt,
confirm: true,
automatic_payment_methods: {
enabled: true,
allow_redirects: 'never'
},
metadata: { challenge_id: challenge.id }
}, {
idempotencyKey: `${challenge.id}_${credential.spt}`
});
¶
Server MUST verify the PaymentIntent status is "succeeded"
before returning 200 with Payment-Receipt header¶
If the PaymentIntent fails or requires additional action, server returns 402 with a new challenge¶
Idempotency:¶
Servers SHOULD include an idempotency key derived from the challenge ID and SPT when creating PaymentIntents. This prevents duplicate charges if the client retries a request.¶
Settlement timing:¶
Stripe processes fund transfers asynchronously. Servers SHOULD return
200 immediately after PaymentIntent confirmation (status "succeeded"),
even if final fund settlement to the merchant is pending.¶
Upon successful settlement, servers MUST return a Payment-Receipt header
per Section 5.3 of [I-D.httpauth-payment]. Servers MUST NOT include a
Payment-Receipt header on error responses; failures are communicated via
HTTP status codes and Problem Details.¶
The receipt payload for Stripe charge:¶
| Field | Type | Description |
|---|---|---|
method
|
string |
"stripe"
|
reference
|
string | Stripe PaymentIntent ID (e.g., "pi_1N4...") |
status
|
string |
"success"
|
timestamp
|
string | [RFC3339] confirmation time |
externalId
|
string | OPTIONAL. Echoed from credential payload |
SPTs are single-use tokens. Stripe automatically prevents SPT reuse at the API level, and idempotency keys (Section 9) prevent duplicate PaymentIntent creation. Servers MUST enforce single-use challenge IDs per Section 5.1.3 of [I-D.httpauth-payment] and SHOULD use Stripe idempotency keys to prevent repeated charges. Servers MAY additionally maintain a local replay cache of consumed challenge IDs.¶
Clients MUST verify the payment amount in the challenge matches their expectation before creating an SPT. The SPT itself does not encode the amount, so clients must trust the challenge parameters.¶
Verification checklist:¶
Stripe's SPT model ensures clients never handle raw payment method details, significantly reducing PCI DSS compliance scope.¶
All communication MUST use TLS 1.2 or higher. Stripe Payment Tokens MUST only be transmitted over HTTPS connections.¶
This specification registers the "charge" intent for the "stripe" payment method in the Payment Intent Registry per Section 13.4 of [I-D.httpauth-payment]:¶
Contact: Stripe (stevekaliski@stripe.com) and Tempo Labs (brendan@tempo.xyz)¶
stripe-charge-challenge = "Payment" 1*SP "id=" quoted-string "," "realm=" quoted-string "," "method=" DQUOTE "stripe" DQUOTE "," "intent=" DQUOTE "charge" DQUOTE "," "request=" base64url-nopad stripe-charge-credential = "Payment" 1*SP base64url-nopad ; Base64url encoding without padding per RFC 4648 Section 5 base64url-nopad = 1*( ALPHA / DIGIT / "-" / "_" )¶
Step 1: Client requests resource¶
GET /api/generate HTTP/1.1 Host: api.example.com¶
Step 2: Server issues payment challenge¶
HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="ch_1a2b3c4d5e",
realm="api.example.com",
method="stripe",
intent="charge",
request="eyJhbW91bnQiOiI1MDAwIiwiY3VycmVuY3kiOiJ1c2QiLCJkZXNjcmlwdGlvbiI6IkFJIGdlbmVyYXRpb24ifQ"
Cache-Control: no-store
Content-Type: application/json
{
"type": "https://paymentauth.org/problems/payment-required",
"title": "Payment Required",
"status": 402,
"detail": "This resource requires payment"
}
¶
Decoded request: ~~~ json { "amount": "5000", "currency": "usd", "description": "AI generation" } ~~~¶
Step 3: Client creates SPT and submits credential¶
GET /api/generate HTTP/1.1 Host: api.example.com Authorization: Payment eyJjaGFsbGVuZ2UiOnsiaWQiOiJjaF8xYTJiM2M0ZDVlIiwicmVhbG0iOiJhcGkuZXhhbXBsZS5jb20iLCJtZXRob2QiOiJzdHJpcGUiLCJpbnRlbnQiOiJjaGFyZ2UiLCJyZXF1ZXN0IjoiZXlKaGJXOTFiblFpT2lJMU1EQXdJaXdpWTNWeWNtVnVZM2tpT2lKMWMyUWlMQ0prWlhOamNtbHdkR2x2YmlJNklrRkpJR2RsYm1WeVlYUnBiMjRpZlEiLCJleHBpcmVzIjoiMjAyNS0wMS0xNVQxMjowNTowMFoifSwicGF5bG9hZCI6eyJzcHQiOiJzcHRfMU40WnYzMmVadktZbG8yQ1BoVlBrSmxXIn19¶
Decoded credential: ~~~ json { "challenge": { "id": "ch_1a2b3c4d5e", "realm": "api.example.com", "method": "stripe", "intent": "charge", "request": "eyJhbW91bnQiOiI1MDAwIiwiY3VycmVuY3kiOiJ1c2QiLCJkZXNjcmlwdGlvbiI6IkFJIGdlbmVyYXRpb24ifQ", "expires": "2025-01-15T12:05:00Z" }, "payload": { "spt": "spt_1N4Zv32eZvKYlo2CPhVPkJlW" } } ~~~¶
Step 4: Server processes payment and returns resource¶
HTTP/1.1 200 OK Payment-Receipt: eyJtZXRob2QiOiJzdHJpcGUiLCJyZWZlcmVuY2UiOiJwaV8xTjRadjMyZVp2S1lsbzJDUGhWUGtKbFciLCJzdGF0dXMiOiJzdWNjZXNzIiwidGltZXN0YW1wIjoiMjAyNS0wMS0xNVQxMjowNDozMloifQ Cache-Control: private Content-Type: text/plain Here is your generated content...¶
Decoded receipt: ~~~ json { "method": "stripe", "reference": "pi_1N4Zv32eZvKYlo2CPhVPkJlW", "status": "success", "timestamp": "2025-01-15T12:04:32Z" } ~~~¶
The authors thank the Tempo community for their feedback on this specification.¶