Representative interview topic

Backend interview: How do you safely handle HTTP 425 Too Early and 0-RTT replay?

BackendHard
Offer.cc Editorial TeamPublished Updated

Question

A payment API intermittently receives HTTP 425 Too Early. Explain the replay risk of 0-RTT, the relationship between the Early-Data header and 425, and a safe client, gateway, and server design.

Prompt and context

A payment API enables TLS 1.3 session resumption. Some requests arrive before the handshake completes and the gateway returns 425 Too Early. Explain which requests may use early data, how to prevent replay side effects, how proxies signal it, and when a client may retry.

What the interviewer evaluates

  • Understanding that 0-RTT reduces latency but does not provide ordinary-handshake replay protection.
  • Correctly distinguishing 425, a network timeout, and a business rejection, and using Early-Data: 1.
  • Connecting idempotency keys, a deduplication window, gateway policy, and the server state machine.
  • Proving with metrics and drills that retries do not charge twice.

Clarifying questions

  1. Which client, CDN, gateway, or origin terminates TLS and can know that data is early?
  2. Is the request a GET or read, or does it charge, ship, grant, or publish a side effect?
  3. Is there a globally unique idempotency key, how long is it retained, and is it shared across regions?
  4. Does one gateway generate 425, and can every SDK understand it and rebuild the body?
  5. How do the retry deadline, payment authorization lifetime, and user-visible state align?

30-second answer

0-RTT lets a TLS 1.3 resuming client send application data early, but an attacker may replay that data. A server should reject early data for side-effecting requests; a supporting intermediary can signal it with Early-Data: 1 and the origin can return 425. After a full handshake, a client retries only when replay is safe. For payment writes it first checks the idempotency result or waits for business confirmation; 425 is not proof that a charge failed.

Deep-dive answer

Step 1: Define the 0-RTT boundary

TLS 1.3 resumption permits early data to save a round trip. The server must not treat it as fresh, non-replayable proof: an attacker may copy the same bytes and send them again during the permitted window.

Step 2: Classify side effects

Public GETs, read-only queries, or strictly idempotent requests may consider early data when protocol and business conditions allow it. Charging, creating an order, granting entitlement, or publishing a message should disable it unless the server has reliable idempotency and atomic deduplication.

Step 3: Understand Early-Data and 425

An intermediary that supports early data may add Early-Data: 1 when forwarding it. An origin unwilling to accept replay risk returns 425, meaning the request arrived too early; the client should resend after the handshake. Absence of the header is not proof of zero replay risk, so verify the complete deployment.

Step 4: Design gateway policy

The gateway maintains an allowlist by method, path, and authentication type. It rejects early data on payment, inventory, and authorization-change paths, while recording connection state for read paths. It must preserve 425 instead of rewriting it to a generic 500.

Step 5: Design idempotency and deduplication

The client creates an unpredictable key for each business intent. The server atomically commits the side effect and the deduplication result, or uses an equivalent atomic boundary. A duplicate key returns the first result and never charges again. The TTL covers maximum network retries, queue delay, and reconciliation.

Step 6: Specify safe retries

After 425, establish a full connection and resend only while the deadline is valid and the body can be rebuilt. GET may retry automatically. A payment request with an idempotency key first reads its state; a write without replay protection goes to business confirmation. Cap retries per intent and use backoff.

Step 7: Verify and observe

Record early-data volume, 425 rate, retry rate by path, duplicate keys, successful charges, and reconciliation differences. Inject proxy and client failures to test rejection, full-handshake retry, timeout, duplicate delivery, and cross-region deduplication. Acceptance must show at most one side effect per business intent.

Model answer

I would put payment writes on the early-data denylist. If the TLS terminator receives early data, it forwards Early-Data: 1; the origin returns 425 for that write and keeps a diagnostic request ID. After a full handshake the client does not blindly replay: it queries the order or charge by idempotency key and retries once only when the state is unknown and the deadline permits. The server atomically stores the key, business result, and deduplication record, with a cross-region boundary. Read-only GETs may retry automatically. I monitor 425s, duplicate keys, and reconciliation differences, and drill the path to prove no double charge.

Common mistakes

  • Assuming TLS 1.3 0-RTT is automatically replay-safe.
  • Retrying 425 forever or rewriting it as 500.
  • Deciding safety only from the HTTP method; some GETs still cause side effects.
  • Keeping the idempotency key only on the client without atomic server deduplication.
  • Treating a timeout as proof that the server did nothing instead of querying state.

Follow-up questions and answers

Follow-up 1: How does 425 differ from 503?

425 addresses replay risk in early data and invites reassessment after a full handshake. 503 means temporary service unavailability; its retry policy depends on capacity and Retry-After.

Follow-up 2: Who adds Early-Data?

An intermediary that understands the mechanism adds Early-Data: 1 while forwarding early data to the origin. Verify the TLS termination point and that proxies neither drop nor forge the signal.

Follow-up 3: Is an idempotency key enough for 0-RTT payments?

No. The key must be unpredictable, atomically persisted with the result, deduplicated across the required regions, retained long enough, and proven to return the same result on duplicates.

Follow-up 4: Why query after a client timeout?

A timeout proves only that the client saw no response. Querying the idempotency state distinguishes succeeded, processing, and not-executed, avoiding a second charge.

Follow-up 5: How would you canary it?

Start with read-only paths and a small client cohort. Track 425 and retry success by path; automatically disable early data on duplicate side effects, reconciliation anomalies, or missing proxy signals.

Public sources

Related questions