Skip to main content
4xx Client Error

428 Precondition Required

The server requires the request to be conditional to prevent the 'lost update' problem.

Meaning & Description

The HTTP 428 Precondition Required status code indicates that the origin server requires the request to be conditional. Its primary purpose is to prevent the 'lost update' problem. This occurs when a client GETs a resource's state, modifies it, and PUTs it back to the server, while a third party has modified the state on the server in the meantime, leading to a conflict. By returning 428, the server forces the client to use a conditional header, typically `If-Match`, to ensure they are operating on the most recent version of the resource.

Common Causes

  • The client failed to include an `If-Match` header with the ETag of the resource being updated.
  • The client failed to include an `If-Unmodified-Since` header with the last known modification date.
  • The API documentation wasn't followed regarding concurrency control requirements.

How to fix a 428 error

  1. Perform a GET request to the resource to obtain its current `ETag` or `Last-Modified` header.
  2. Include the retrieved ETag in the `If-Match` header of your PUT/PATCH/DELETE request.
  3. If the server responds with a 412 Precondition Failed after adding the header, fetch the resource again and merge any upstream changes before retrying.

Browser & SEO Behaviour

Browser Behavior

Browsers rarely encounter 428 errors during typical web navigation, as it's primarily used for API interactions. If encountered, the raw response is usually displayed.

SEO Impact

None. Crawlers only use GET requests and do not attempt state-changing operations that require preconditions.

CDN Behavior

CDNs pass 428 responses through to the client without caching, as they indicate a state-modification failure.

Code Examples

HTTP
PUT /api/articles/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json

{"title": "Updated Title"}
Node.js (Express)
app.put('/api/inventory/:id', (req, res) => {
  if (!req.headers['if-match']) {
    return res.status(428).json({
      error: "Precondition Required",
      message: "You must provide an If-Match header with the latest ETag to update this resource."
    });
  }
  // Proceed with validation
});
Python (FastAPI)
@app.put("/api/users/{user_id}")
def update_user(user_id: int, if_match: str = Header(None)):
    if not if_match:
        raise HTTPException(status_code=428, detail="If-Match header is missing")
    # Check if ETag matches current DB state...
    return {"status": "success"}
Go (net/http)
func updateArticle(w http.ResponseWriter, r *http.Request) {
    if r.Header.Get("If-Match") == "" {
        http.Error(w, "Precondition Required: If-Match header missing", 428)
        return
    }
    // Handle update logic
}
C# (ASP.NET Core)
[HttpPut("{id}")]
public IActionResult Update(int id, [FromHeader(Name = "If-Match")] string eTag) {
    if (string.IsNullOrEmpty(eTag)) {
        return StatusCode(428, "An If-Match header is required for updates.");
    }
    // Check ETag and update DB
    return Ok();
}
curl
curl -X PUT https://api.example.com/items/1 \
  -H "If-Match: \"33a64df551425fcc55e4d42a148795d9f25f89d4\"" \
  -H "Content-Type: application/json" \
  -d '{"stock": 4}' \
  -i

Raw HTTP Response Example

HTTP Response
HTTP/1.1 428 Precondition Required
Content-Type: application/json
Content-Length: 104

{
  "error": "Precondition Required",
  "message": "Please include an If-Match header with the ETag."
}

Real-world Examples

GitHub API: Certain high-stakes configuration endpoints require passing an ETag to ensure the configuration hasn't been modified by another admin.
Stripe: Uses idempotent keys and versioning to handle concurrency, though conceptually similar to the 'lost update' problem 428 solves.
Elasticsearch: Requires sequence numbers and primary terms to implement optimistic concurrency control, rejecting updates if these are missing.

Frequently Asked Questions

What is the 'lost update' problem?

It occurs when Client A and Client B both read a resource. Client A updates it, and then Client B updates it, accidentally overwriting Client A's changes because Client B was unaware of them.

What is the difference between 428 Precondition Required and 412 Precondition Failed?

428 means you didn't provide a precondition header (like `If-Match`) at all. 412 means you *did* provide one, but it didn't match the current state on the server (e.g., your ETag was outdated).

Which HTTP headers are considered preconditions?

The primary precondition headers are `If-Match`, `If-None-Match`, `If-Modified-Since`, `If-Unmodified-Since`, and `If-Range`.

Do I always need to return 428 for PUT requests?

No. You only return 428 if your specific API endpoint strictly enforces optimistic concurrency control. For many simple APIs, last-write-wins is acceptable.

How do I fix a 428 error?

Fetch the resource again using a GET request, capture the `ETag` from the response headers, and include that value in an `If-Match` header in your subsequent PUT/PATCH request.

Did You Know?

RFC 6585 introduced the 428 status code in 2012, along with 429 Too Many Requests and 431 Request Header Fields Too Large.

Implementing optimistic concurrency control via ETags and 428/412 is universally preferred in REST over pessimistic locking (423 Locked).

ETags (Entity Tags) are essentially hashes or version numbers of the resource state.

Developer Tips

  • Always provide a helpful error message with a 428 response instructing the developer to fetch the latest state and use an `If-Match` header.
  • If your database uses a `version` or `updated_at` column, use it to generate your ETags for seamless concurrency control.
  • Don't enforce 428 on idempotent DELETE requests unless business logic explicitly requires ensuring the client knows exactly what they are deleting.

Interview Questions

These are questions you might face in a backend or API design interview that touch on HTTP 428.

What HTTP status code is used to prevent the 'lost update' problem, and how?

The 428 Precondition Required status code prevents the lost update problem by forcing clients to include conditional headers (like If-Match). This ensures they are updating the latest known state.

Explain the difference between optimistic and pessimistic locking in HTTP.

Optimistic locking assumes conflicts are rare; it uses ETags and precondition headers (428, 412) to fail updates only when a conflict actually occurs. Pessimistic locking (423 Locked) explicitly locks the resource so nobody else can touch it until the lock is released.

If a client sends an `If-Match` header but the ETag doesn't match the server's current version, what status code should be returned?

The server should return 412 Precondition Failed. 428 is only used when the precondition header is completely missing.

Common Interview Mistakes

  • Confusing 428 Precondition Required with 412 Precondition Failed.
  • Failing to explain the 'lost update' problem or optimistic concurrency control.
  • Assuming 428 relates to missing API keys or authorization headers.