| Internet-Draft | Payment Auth Scheme | March 2026 |
| Ryan, et al. | Expires 4 September 2026 | [Page] |
This document defines the "Payment" HTTP authentication scheme, enabling HTTP resources to require a payment challenge to be fulfilled before access. The scheme extends HTTP Authentication, using the HTTP 402 "Payment Required" status code.¶
The protocol is payment-method agnostic, supporting any payment network or currency through registered payment method identifiers. Specific payment methods are defined in separate payment method specifications.¶
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.¶
HTTP 402 "Payment Required" was reserved in HTTP/one-point-one [RFC9110] but never standardized for common use. This specification defines the "Payment" authentication scheme that gives 402 its semantics, enabling resources to require a payment challenge to be fulfilled before access.¶
This specification defines the abstract protocol framework. Concrete payment methods are defined in payment method specifications that:¶
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 WWW-Authenticate header with scheme "Payment" indicating the
payment requirements for accessing a resource.¶
An Authorization header with scheme "Payment" containing payment
authorization data.¶
A mechanism for transferring value, identified by a registered identifier.¶
The type of payment request, identified by a registered value in the IANA "HTTP Payment Intents" registry. Intents are defined by separate intent specifications.¶
Method-specific data in the challenge enabling payment completion.
Encoded as base64url JSON in the request parameter.¶
Method-specific data in the credential proving payment.¶
Client Server
│ │
│ (1) GET /resource │
├────────────────────────────────────────────────>│
│ │
│ (2) 402 Payment Required │
│ WWW-Authenticate: Payment id="..", │
│ method="..", intent="..", request=".." │
│<────────────────────────────────────────────────┤
│ │
│ (3) Client fulfills payment challenge │
│ (signs transaction, pays invoice, etc.) │
│ │
│ (4) GET /resource │
│ Authorization: Payment <credential> │
├────────────────────────────────────────────────>│
│ │
│ (5) Server verifies and settles │
│ │
│ (6) 200 OK │
│ Payment-Receipt: <receipt> │
│<────────────────────────────────────────────────┤
│ │
¶
The following table defines how servers MUST respond to payment-related conditions.¶
| Condition | Status | Response |
|---|---|---|
| Resource requires payment, no credential provided | 402 | Fresh challenge in WWW-Authenticate
|
| Malformed credential (invalid base64url, bad JSON) | 402 | Fresh challenge + malformed-credential problem |
Unknown, expired, or already-used challenge id
|
402 | Fresh challenge + invalid-challenge problem |
| Payment proof invalid or verification failed | 402 | Fresh challenge + verification-failed problem |
| Payment verified, access granted | 200 | Resource + optional Payment-Receipt
|
| Payment verified, but policy denies access | 403 | No challenge (payment was valid) |
Servers MUST return 402 with a WWW-Authenticate: Payment header when
payment is required or when a payment credential fails validation
(see Section 4.4 for details).¶
Error details are provided in the response body using Problem Details
[RFC9457] rather than in the WWW-Authenticate header parameters.¶
Servers SHOULD return 402 when:¶
The resource requires payment as a precondition for access¶
The server can provide a Payment challenge that the client may fulfill¶
Payment is the primary barrier to access (not authentication or authorization)¶
Servers MAY return 402 when:¶
Servers SHOULD NOT return 402 when:¶
The client lacks authentication credentials (use 401)¶
The client is authenticated but lacks authorization (use 403)¶
The resource does not exist (use 404)¶
No Payment challenge can be constructed for the request¶
Servers MUST NOT return 402 without including a WWW-Authenticate header
containing at least one Payment challenge.¶
When a resource requires both authentication and payment, servers SHOULD:¶
First verify authentication credentials¶
Return 401 if authentication fails¶
Return 402 with a Payment challenge only after successful authentication¶
This ordering prevents information leakage about payment requirements to unauthenticated clients.¶
The Payment challenge is sent in the WWW-Authenticate header per
[RFC9110]. The challenge uses the auth-param syntax defined in Section 11
of [RFC9110]:¶
challenge = "Payment" [ 1*SP auth-params ] auth-params = auth-param *( OWS "," OWS auth-param ) auth-param = token BWS "=" BWS ( token / quoted-string )¶
id: Unique challenge identifier. Servers MUST bind this value to the
challenge parameters (Section 5.1.3) to enable verification. Clients MUST
include this value unchanged in the credential.¶
realm: Protection space identifier per [RFC9110]. Servers MUST
include this parameter to define the scope of the payment requirement.¶
method: Payment method identifier (Section 6). MUST be a lowercase
ASCII string.¶
intent: Payment intent type (Section 7). The value MUST be a
registered entry in the IANA "HTTP Payment Intents" registry.¶
request: Base64url-encoded [RFC4648] JSON [RFC8259] containing
payment-method-specific data needed to complete payment. Structure is
defined by the payment method specification. Padding characters ("=")
MUST NOT be included. The JSON MUST be serialized using JSON
Canonicalization Scheme (JCS) [RFC8785] to ensure deterministic
encoding across implementations. This is critical for challenge binding
(Section 5.1.2.1): since the HMAC input includes the base64url-encoded
request as it appears on the wire, different JSON serialization orders
would produce different HMAC values, breaking cross-implementation
interoperability.¶
digest: Content digest of the request body, formatted per [RFC9530].
Servers SHOULD include this parameter when the payment challenge applies
to a request with a body (e.g., POST, PUT, PATCH). When present, clients
MUST submit the credential with a request body whose digest matches this
value. See Section 5.1.3 for body binding requirements.¶
expires: Timestamp indicating when this challenge expires, formatted
as an [RFC3339] date-time string (e.g., "2025-01-15T12:00:00Z").
Servers SHOULD include this parameter. Clients MUST NOT submit
credentials for expired challenges.¶
description: Human-readable description of the resource or payment
purpose. This parameter is for display purposes only and MUST NOT be
relied upon for payment verification (see Section 11.6).¶
opaque: Base64url-encoded [RFC4648] JSON [RFC8259] containing
server-defined correlation data (e.g., a payment processor intent
identifier). The value MUST be a JSON object whose values are strings
(a flat string-to-string map). Clients MUST return this parameter
unchanged in the credential and MUST NOT modify it. The JSON MUST be
serialized using JSON Canonicalization Scheme (JCS) [RFC8785] before
base64url encoding. Servers SHOULD include opaque in the challenge
binding (Section 5.1.2.1) to ensure tamper protection.¶
Unknown parameters MUST be ignored by clients.¶
Servers SHOULD bind the challenge id to the challenge parameters (Section 5.1.1 and Section 5.1.2) to prevent request integrity attacks where a client could
sign or submit a payment different from what the server intended. Servers
MUST verify that credentials present an id matching the expected binding.¶
The binding mechanism is implementation-defined. Servers MAY use stateful storage (e.g., database lookup) or stateless verification (e.g., HMAC, authenticated encryption) to validate the binding.¶
Servers using HMAC-SHA256 for stateless challenge binding SHOULD compute
the challenge id as follows:¶
The HMAC input is constructed from exactly seven fixed positional
slots. Required fields supply their string value; optional fields use
an empty string ("") when absent. The slots are:¶
| Slot | Field | Value |
|---|---|---|
| 0 |
realm
|
Required. String value. |
| 1 |
method
|
Required. String value. |
| 2 |
intent
|
Required. String value. |
| 3 |
request
|
Required. JCS-serialized per [RFC8785], then base64url-encoded. |
| 4 |
expires
|
Optional. String value if present; empty string if absent. |
| 5 |
digest
|
Optional. String value if present; empty string if absent. |
| 6 |
opaque
|
Optional. JCS-serialized per [RFC8785], then base64url-encoded if present; empty string if absent. |
The computation proceeds as follows:¶
Populate all seven slots as described above.¶
Join all seven slots with the pipe character (|) as delimiter.
Every slot is always present in the joined string; absent optional
fields appear as empty segments (e.g., ...|expires||opaque_b64url
when digest is absent).¶
Compute HMAC-SHA256 over the resulting string using a server secret.¶
Encode the HMAC output as base64url without padding ([RFC4648] Section 5).¶
input = "|".join([
realm,
method,
intent,
request_b64url,
expires or "",
digest or "",
opaque_b64url or "",
])
id = base64url(HMAC-SHA256(server_secret, input))
¶
Optional fields use fixed positional slots with empty strings when
absent, rather than being omitted. This avoids ambiguity between
combinations of optional fields — for example, (expires set, no
digest) and (no expires, digest set) produce distinct inputs — and
ensures that adding a new optional slot in a future revision does not
change the HMAC for challenges that omit it.¶
HTTP/1.1 402 Payment Required
Cache-Control: no-store
WWW-Authenticate: Payment id="x7Tg2pLqR9mKvNwY3hBcZa",
realm="api.example.com",
method="example",
intent="charge",
expires="2025-01-15T12:05:00Z",
request="eyJhbW91bnQiOiIxMDAwIiwiY3VycmVuY3kiOiJVU0QiLCJyZWNpcGllbnQiOiJhY2N0XzEyMyJ9"
¶
Decoded request example:¶
{
"amount": "1000",
"currency": "usd",
"recipient": "acct_123"
}
¶
Servers SHOULD include the digest parameter when issuing challenges for
requests with bodies. The digest value is computed per [RFC9530]:¶
WWW-Authenticate: Payment id="...",
realm="api.example.com",
method="example",
intent="charge",
digest="sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:",
expires="2025-01-15T12:05:00Z",
request="..."
¶
When verifying a credential with a digest parameter, servers MUST:¶
Servers SHOULD include a Payment-Receipt header on successful responses:¶
Payment-Receipt = base64url-nopad¶
The decoded JSON object contains:¶
| Field | Type | Description |
|---|---|---|
status
|
string |
"success" — receipts are only issued on successful payment |
method
|
string | Payment method used |
timestamp
|
string | ISO 8601 settlement time |
reference
|
string | Method-specific reference (tx hash, invoice id, etc.) |
Payment method specifications MAY define additional fields for receipts.¶
The status field MUST be "success", indicating the payment was
verified and settled successfully. Receipts are only issued on
successful payment responses (2xx status codes).¶
Servers MUST NOT return a Payment-Receipt header on error responses.
Payment failures are communicated via HTTP status codes and Problem
Details [RFC9457]. Servers MUST return 402 with a fresh challenge
and appropriate problem type when payment verification fails.¶
Payment methods are identified by lowercase ASCII strings:¶
payment-method-id = method-name [ ":" sub-method ] method-name = 1*LOWERALPHA sub-method = 1*( LOWERALPHA / DIGIT / "-" )¶
Method identifiers are case-sensitive and MUST be lowercase.¶
The optional sub-method component allows payment methods to specify
variants, networks, or chains. Payment method specifications MUST define
the semantics of their sub-methods.¶
Payment methods are registered in the HTTP Payment Methods registry
(Section 12.3). Each registered method has an associated specification
that defines the request and payload schemas.¶
Payment intents describe the type of payment being requested.¶
intent = 1*( ALPHA / DIGIT / "-" )¶
Payment intents are defined in separate intent specifications that:¶
Define the semantic meaning of the intent¶
Specify required and optional request fields¶
Specify payload requirements¶
Define verification and settlement semantics¶
Register the intent in the Payment Intent Registry (Section 12.4)¶
See the Payment Intent Registry for registered intents.¶
If a server supports multiple intents, it MAY issue multiple challenges:¶
WWW-Authenticate: Payment id="abc", realm="api.example.com", method="example", intent="charge", request="..." WWW-Authenticate: Payment id="def", realm="api.example.com", method="example", intent="authorize", request="..."¶
Clients choose which challenge to respond to. Clients that do not recognize an intent SHOULD treat the challenge as unsupported.¶
Servers SHOULD return Problem Details [RFC9457] error bodies with 402 responses:¶
{
"type": "https://paymentauth.org/problems/payment-required",
"title": "Payment Required",
"status": 402,
"detail": "Human-readable description"
}
¶
The type URI SHOULD correspond to one of the problem types defined
below, and the canonical base URI for problem types is
https://paymentauth.org/problems/.¶
| Code | HTTP | Description |
|---|---|---|
payment-required
|
402 | Resource requires payment |
payment-insufficient
|
402 | Amount too low |
payment-expired
|
402 | Challenge or authorization expired |
verification-failed
|
402 | Proof invalid |
method-unsupported
|
400 | Method not accepted |
malformed-credential
|
402 | Invalid credential format |
invalid-challenge
|
402 | Challenge ID unknown, expired, or already used |
Servers SHOULD use the Retry-After HTTP header [RFC9110] to indicate
when clients may retry:¶
HTTP/1.1 402 Payment Required Retry-After: 60 WWW-Authenticate: Payment ...¶
Payment method specifications MUST define:¶
Method Identifier: Unique lowercase string¶
Request Schema: JSON structure for the request parameter¶
Payload Schema: JSON structure for credential payloads¶
Verification Procedure: How servers validate proofs¶
Settlement Procedure: How payment is finalized¶
Security Considerations: Method-specific threats and mitigations¶
The Payment scheme uses a layered versioning strategy:¶
The Payment scheme name is the stable identifier. The core protocol
does NOT carry a version on the wire, consistent with all deployed HTTP
authentication schemes (Basic, Bearer, Digest). Evolution happens
through adding optional parameters and fields; implementations MUST
ignore unknown parameters and fields. If a future change is truly
incompatible, a new scheme name (e.g., Payment2) would be registered.¶
Payment method specifications MAY include a version field in their
methodDetails. The absence of a version field is implicitly
version 1. When a breaking change is needed, the method specification
adds a version field starting at 2. Compatible changes (adding
optional fields, defining defaults) do not require a version change.
Methods MAY also register a new identifier for changes fundamental
enough to warrant a distinct name.¶
Payment intents do not carry a version. They evolve through the same
compatibility rules as the core: adding optional fields with defined
defaults is compatible, and breaking changes require a new intent
identifier (e.g., charge-v2).¶
Implementations MAY define additional parameters in challenges:¶
Servers SHOULD keep challenges under 8KB. Clients MUST be able to handle challenges of at least 4KB. Servers MUST be able to handle credentials of at least 4KB.¶
The description parameter may contain localized text. Servers SHOULD
use the Accept-Language request header [RFC9110] to determine the
appropriate language.¶
This specification assumes:¶
This specification REQUIRES TLS 1.2 [RFC5246] or later for all Payment authentication flows. TLS 1.3 [RFC8446] is RECOMMENDED.¶
Implementations MUST use TLS when transmitting Payment challenges and credentials. Payment credentials contain sensitive authorization data that could result in financial loss if intercepted.¶
Servers MUST NOT issue Payment challenges over unencrypted HTTP. Clients MUST NOT send Payment credentials over unencrypted HTTP. Implementations SHOULD reject Payment protocol messages received over non-TLS connections.¶
Payment credentials are bearer tokens that authorize financial transactions. Servers and intermediaries MUST NOT log Payment credentials or include them in error messages, debugging output, or analytics. Credential exposure could enable replay attacks or unauthorized payments.¶
Implementations MUST treat Payment credentials with the same care as authentication passwords or session tokens. Credentials SHOULD be stored only in memory and cleared after use.¶
Payment methods used with this specification MUST provide single-use proof semantics. A payment proof MUST be usable exactly once; subsequent attempts to use the same proof MUST be rejected by the payment method infrastructure.¶
Servers MUST NOT perform side effects (database writes, external API calls, resource creation) for requests that have not been paid. The unpaid request that triggers a 402 challenge MUST NOT modify server state beyond recording the challenge itself.¶
For non-idempotent methods (POST, PUT, DELETE), servers SHOULD accept
an Idempotency-Key header to enable safe client retries. When a client
retries a request with the same Idempotency-Key and a valid Payment
credential, the server SHOULD return the same response as the original
successful request without re-executing the operation.¶
Servers MUST ensure that concurrent requests with the same Payment credential result in at most one successful payment settlement and one resource delivery. Race conditions between parallel requests could otherwise cause double-payment or double-delivery.¶
Implementations SHOULD use atomic operations or distributed locks when verifying and consuming Payment credentials. The credential verification and resource delivery SHOULD be performed as an atomic operation where possible.¶
Clients MUST verify before authorizing payment:¶
Requested amount is reasonable for the resource¶
Recipient/address is expected¶
Currency/asset is as expected¶
Validity window is appropriate¶
Clients MUST NOT rely on the description parameter for payment
verification. Malicious servers could provide a misleading description
while the actual request payload requests a different amount.¶
Implementations MUST treat Authorization: Payment headers and
Payment-Receipt headers as sensitive data.¶
HTTP intermediaries (proxies, caches, CDNs) may not recognize 402 as an
authentication challenge in the same way they handle 401. While this
specification uses WWW-Authenticate headers with 402 responses following
the same syntax as [RFC9110], intermediaries that perform special
processing for 401 (such as stripping credentials or triggering
authentication prompts) may not apply the same behavior to 402.¶
Servers SHOULD NOT rely on intermediary-specific handling of 402 responses. Clients MUST be prepared to receive 402 responses through any intermediary.¶
Payment challenges contain unique identifiers and time-sensitive payment data that MUST NOT be cached or reused. To prevent challenge replay and stale payment information:¶
Servers MUST send Cache-Control: no-store [RFC9111] with 402 responses; this ensures no shared cache reuse.¶
Responses containing Payment-Receipt headers MUST include
Cache-Control: private to prevent shared caches from storing
payment receipts.¶
Clients (particularly browser-based wallets) SHOULD:¶
Servers SHOULD implement rate limiting on challenges issued and credential verification attempts.¶
This document registers the "Payment" authentication scheme in the "Hypertext Transfer Protocol (HTTP) Authentication Scheme Registry" established by [RFC9110]:¶
This document registers the following header fields:¶
| Field Name | Status | Reference |
|---|---|---|
| Payment-Receipt | permanent | This document, Section 5.3 |
This document establishes the "HTTP Payment Methods" registry. This registry uses the "Specification Required" policy defined in [RFC8126].¶
Registration requests must include:¶
This document establishes the "HTTP Payment Intents" registry. This registry uses the "Specification Required" policy defined in [RFC8126].¶
Registration requests must include:¶
Intent Identifier: Unique lowercase ASCII string¶
Description: Brief description of the intent semantics¶
Specification pointer: Reference to the specification document¶
Registrant Contact: Contact information for the registrant¶
The registry is initially empty. Intent specifications register their identifiers upon publication.¶
; HTTP Authentication Challenge (following RFC 7235 Section 2.1) payment-challenge = "Payment" [ 1*SP auth-params ] auth-params = auth-param *( OWS "," OWS auth-param ) auth-param = token BWS "=" BWS ( token / quoted-string ) ; Required parameters: id, realm, method, intent, request ; Optional parameters: expires, description ; HTTP Authorization Credentials payment-credentials = "Payment" 1*SP base64url-nopad ; Payment-Receipt header field value Payment-Receipt = base64url-nopad ; Base64url encoding without padding per RFC 4648 Section 5 base64url-nopad = 1*( ALPHA / DIGIT / "-" / "_" ) ; Payment method identifier (lowercase only) payment-method-id = method-name [ ":" sub-method ] method-name = 1*LOWERALPHA sub-method = 1*( LOWERALPHA / DIGIT / "-" ) LOWERALPHA = %x61-7A ; a-z ; Payment intent intent-token = 1*( ALPHA / DIGIT / "-" )¶
A client requests a resource, receives a payment challenge, fulfills the payment, and receives the resource with a receipt.¶
Client Server │ │ │ (1) GET /resource │ ├─────────────────────────────────────>│ │ │ │ (2) 402 Payment Required │ │ WWW-Authenticate: Payment ... │ │<─────────────────────────────────────┤ │ │ │ (3) Fulfill payment challenge │ │ (method-specific) │ │ │ │ (4) GET /resource │ │ Authorization: Payment ... │ ├─────────────────────────────────────>│ │ │ │ (5) 200 OK │ │ Payment-Receipt: ... │ │<─────────────────────────────────────┤ │ │¶
Challenge:¶
HTTP/1.1 402 Payment Required
Cache-Control: no-store
Content-Type: application/problem+json
WWW-Authenticate: Payment id="qB3wErTyU7iOpAsD9fGhJk",
realm="api.example.com",
method="invoice",
intent="charge",
expires="2025-01-15T12:05:00Z",
request="eyJhbW91bnQiOiIxMDAwIiwiY3VycmVuY3kiOiJVU0QiLCJpbnZvaWNlIjoiaW52XzEyMzQ1In0"
{
"type": "https://paymentauth.org/problems/payment-required",
"title": "Payment Required",
"status": 402,
"detail": "Payment required for access.",
"challengeId": "qB3wErTyU7iOpAsD9fGhJk"
}
¶
Decoded request:¶
{
"amount": "1000",
"currency": "usd",
"invoice": "inv_12345"
}
¶
Credential:¶
GET /resource HTTP/1.1 Host: api.example.com Authorization: Payment eyJpZCI6InFCM3dFclR5VTdpT3BBc0Q5ZkdoSmsiLCJwYXlsb2FkIjp7InByZWltYWdlIjoiMHhhYmMxMjMuLi4ifX0¶
Decoded credential:¶
{
"challenge": {
"id": "qB3wErTyU7iOpAsD9fGhJk",
"realm": "api.example.com",
"method": "invoice",
"intent": "charge",
"request": "eyJhbW91bnQiOiIxMDAwIiwiY3VycmVuY3kiOiJVU0QiLCJpbnZvaWNlIjoiaW52XzEyMzQ1In0",
"expires": "2025-01-15T12:05:00Z"
},
"payload": {
"preimage": "0xabc123..."
}
}
¶
Success:¶
HTTP/1.1 200 OK
Cache-Control: private
Payment-Receipt: eyJzdGF0dXMiOiJzdWNjZXNzIiwibWV0aG9kIjoiaW52b2ljZSIsInRpbWVzdGFtcCI6IjIwMjUtMDEtMTVUMTI6MDA6MDBaIiwicmVmZXJlbmNlIjoiaW52XzEyMzQ1In0
Content-Type: application/json
{"data": "..."}
¶
Servers MAY return multiple Payment challenges in a single 402 response, each with a different payment method or configuration:¶
HTTP/1.1 402 Payment Required Cache-Control: no-store WWW-Authenticate: Payment id="pT7yHnKmQ2wErXsZ5vCbNl", realm="api.example.com", method="invoice", intent="charge", request="..." WWW-Authenticate: Payment id="mF8uJkLpO3qRtYsA6wDcVb", realm="api.example.com", method="signed", intent="charge", request="..."¶
When a server returns multiple challenges, clients SHOULD select one
based on their capabilities and user preferences. Clients MUST send
only one Authorization: Payment header in the subsequent request,
corresponding to the selected challenge.¶
Servers receiving multiple Payment credentials in a single request SHOULD reject with 400 (Bad Request).¶
HTTP/1.1 402 Payment Required
Cache-Control: no-store
Content-Type: application/problem+json
WWW-Authenticate: Payment id="aB1cDeF2gHiJ3kLmN4oPqR", realm="api.example.com", method="invoice", intent="charge", request="..."
{
"type": "https://paymentauth.org/problems/verification-failed",
"title": "Payment Verification Failed",
"status": 402,
"detail": "Invalid payment proof."
}
¶
The server returns 402 with a fresh challenge, allowing the client to retry with a new payment credential.¶