Question
You need to add pay-per-request access to a data API. An unpaid request should return HTTP 402, and the client should retry the original request after paying. Explain the status of 402 in RFC 9110 and design an x402-like end-to-end protocol covering payment requirements, proof verification, resource binding, replay protection, idempotency, refunds, and reconciliation.
What the interviewer is testing
- Whether you distinguish 402's standardized meaning from a concrete payment scheme: RFC 9110 reserves the status code but does not define a payment network, currency, or response format.
- Whether you bind payment proof to the resource, amount, recipient, network, and expiry so a payment cannot be moved to another request.
- Whether you handle client retries, timeouts, duplicate charges, blockchain confirmation delay, and accounting consistency.
- Whether you can state the trust boundaries among the payment service, resource service, settlement party, and audit log.
Model answer
402 is registered as “Payment Required” in the HTTP status code registry. RFC 9110 reserves it but does not define a universal payment protocol. A protocol should treat 402 as a machine-readable challenge, not as proof that payment has already happened.
The resource service can return a unique payment requirement in the 402 response. It should include a resource identifier, method and path, amount, asset, network, recipient, expiry, and nonce. The client signs or pays for exactly those fields. The service or a trusted facilitator verifies the proof, checks the amount, recipient, network, and resource, and then gives the resource service a one-time receipt.
The resource service should record the idempotent relationship between a request id and payment id before executing an expensive operation. A retry with the same request id returns the same result or an explicit processing state. Successful payment does not imply successful resource execution, so payment, authorization, execution, and refund need traceable states. A reconciliation job should find differences among chain confirmation, service records, and actual delivery.
Implementation sketch
The pseudocode below shows the core challenge and retry boundaries; a production system also needs a payment verifier, idempotent storage, and audit logging.
handle(request):
id = request.idempotencyKey
if receiptStore.has(id):
return receiptStore.result(id)
requirement = makeRequirement(
resource = canonicalResource(request),
amount = quote(request),
network = "base",
expiresAt = now + 60s,
nonce = randomBytes(16)
)
proof = request.headers["Payment-Proof"]
if proof is missing:
return 402, { "payment-required": requirement }
payment = verifyProof(proof, requirement)
if payment.invalid or payment.expired or payment.replayed:
return 402, { "payment-required": requirement, "reason": "invalid-proof" }
result = executeOnce(id, request, payment)
receiptStore.put(id, payment.id, result)
return 200, resultThe key invariant is that canonicalResource and verifyProof use the same normalization rules. Otherwise one resource can have multiple string representations and signature verification can disagree with authorization. executeOnce needs a unique constraint, transaction, or durable state so a retry cannot duplicate side effects.
Common pitfalls
- Assuming 402 includes a payment flow. It only says that payment is required; the protocol must define fields and verification rules.
- Checking only the amount while ignoring the resource, network, recipient, asset, or expiry, enabling cross-resource substitution or cross-network replay.
- Executing side effects immediately after payment confirmation without an idempotent request record, so a timeout retry charges or creates the resource twice.
- Treating a submitted chain transaction as final settlement; confirmation delay, reorgs, facilitator failures, and refunds belong in the state machine.
- Putting payment proof in logs or URLs, creating credential leakage and replay risk.
Production trade-offs
For low-value, low-risk reads, a short-lived quote, one-time nonce, and asynchronous final reconciliation may be acceptable. High-value writes should obtain verifiable settlement state before delivery and execute behind an idempotent transaction. If clients do not have wallet or on-chain capabilities, a facilitator can pay on their behalf, but its trust scope, fees, limits, and failure fallback must be explicit.
The protocol also needs rules for price changes, expired challenges, partial payment, payment success followed by resource failure, refunds, and service degradation. A cache must not share a response containing payment proof with another principal; its key must include the authorization result, or it should cache only the public 402 challenge.
References
- RFC 9110 HTTP Semantics: 402 registration semantics and HTTP status constraints.
- x402 Introduction: the challenge-driven, accountless pay-per-request protocol concept.
- Coinbase HTTP 402 Core Concepts: implementation boundaries for payment requirements, verification, and resource access.
Follow-up questions
How do you stop one payment proof from being used for two resources?
Put the normalized method, path, query digest, or resource ID into the payment requirement and cover those fields with the proof. The service recomputes the digest with the same normalization algorithm and records each nonce or payment id as single-use.
Should the 402 challenge be in headers or the response body?
Define a versioned machine-readable format first. Headers suit a small hint, while the body can carry a multi-field payment requirement. Either way, limit its size, declare its content type, and avoid putting sensitive credentials in cacheable headers.
What if payment succeeds but business execution fails?
Keep payment, authorization, execution, and refund as separate states linked by request id. If the operation is not retryable, enter a refund or manual reconciliation queue. If it is retryable, return a processing state and make later reads return the same result.
Does x402 require a blockchain?
Current x402 material uses on-chain or facilitator payments as examples, but HTTP 402 itself does not prescribe a settlement network. Separate the status-code semantics from the payment rail; changing the rail requires redefining proof, finality, and refund semantics.
How do you verify that the system never double-charges?
Add unique constraints for payment id, request id, and the business operation; record every verification and execution result; and inject failures covering client timeouts, service restarts, duplicate verifier callbacks, and delayed reconciliation.