Representative interview topic

Backend Interview: How Would You Design an HTTP 402/x402 Pay-Per-Request API?

BackendHard
Offer.cc Editorial TeamPublished Updated

Question

How would you design an HTTP 402/x402 pay-per-request protocol for a data API while handling payment proof, resource binding, replay protection, idempotent retries, refunds, and reconciliation?

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.

text
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, result

The 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.

Public sources

Related questions