HTTP Headers
HTTP headers are named fields that travel with every request and response and describe the message rather than carry its data. As in the lesson on requests and responses, they sit between the start line and the body as Name: value lines; RFC 9110 calls them header fields. They say what format the body is in, who is asking, how long the answer may be cached and what to remember.
For example, when a shop's app saves a customer's language, the request carries Content-Type: application/json (this body is JSON), Accept: application/json (answer in JSON) and Authorization: Bearer … (this is who I am). The response adds Cache-Control: no-store (keep no copy) and Set-Cookie: lang=ar (remember this). None of it is on screen, yet each line decides whether the feature works and is safe.
Why it matters
Many defects live only in headers. A correct JSON body labelled Content-Type: text/html breaks clients that trust the label. A personal profile sent with Cache-Control: public may be stored by a shared cache, such as a CDN (content delivery network), and served to the next user. A session cookie without Secure may travel over unencrypted HTTP. Each time, the screen looks right and the UI test passes.
Headers also explain failures fast: Content-Type why a body does not parse, WWW-Authenticate which authentication a refused call expected, Cache-Control and Age (how many seconds old a cached copy is) why a fixed page still looks old.
How headers are written and read
Field names are case-insensitive: Content-Type and content-type are the same header, and HTTP/2 and HTTP/3 send every name in lowercase, which is why DevTools often shows content-type. Whether a value is case-sensitive depends on the header: media types such as application/json are not; access tokens and cookie values are.
A header may appear more than once. For list-valued headers such as Accept, a recipient may join the lines into one comma-separated line; RFC 9110 names Set-Cookie as the exception: one cookie per line, never merged. Headers fall into groups:
- Request headers describe the client and what it wants:
Host(required in HTTP/1.1),User-Agent,Accept,Accept-Language,Authorization,Cookie. - Response headers describe the answer:
Set-Cookie,Location,WWW-Authenticate,Retry-After,Server. - Content headers describe the body in either direction:
Content-Type,Content-Length,Content-Encoding.Cache-Controlalso works both ways, with a different meaning in each.
Try it: Request Header Inspector Try it: HTTP Headers Parser
Five headers in almost every test
The exchange below saves a customer's preferred language on a test shop. Each of the five headers answers a different question.
Content-Type labels the body in both directions, Accept and Authorization go with the request, and the response answers with Cache-Control: no-store and a Set-Cookie.Request
PUT /api/v1/me/preferences HTTP/1.1 Host: test.shop.example.com Part 1:Content-Type: application/json Part 2:Accept: application/json Part 3:Authorization: Bearer eyJhbGciOi... Content-Length: 18 {"language": "ar"}
Response
HTTP/1.1 200 OK Part 1:Content-Type: application/json; charset=utf-8 Content-Length: 55 Part 4:Cache-Control: no-store Part 5:Set-Cookie: lang=ar; Path=/; Max-Age=31536000; Secure {"language": "ar", "updatedAt": "2026-10-01T09:30:00Z"}
- 1
Content-TypeLabels the body of each message: JSON going out, JSON with acharsetcoming back. It must match the bytes; a format the server does not accept gets415.request and response - 2
AcceptWhat the client will take back. If nothing matches:406, or the server's default format, as the contract says.request - 3
AuthorizationA scheme and the credentials. Missing or invalid:401withWWW-Authenticate. Mask the value in every report.request - 4
Cache-Controlno-store: no cache may keep a copy, the right rule for personal data.no-cachewould still allow storing with revalidation.response - 5
Set-CookieStoreslang=arfor a year; the browser sends it back inCookie.Securekeeps it off unencrypted HTTP.response
Content-Type: the media type of this message's body, with optional parameters such ascharset. The receiver parses the body by it, so it must match the bytes; a server that does not accept the format should answer415 Unsupported Media Type.Accept: the media types the client will take, optionally weighted (q=0.8). If none is available, RFC 9110 lets the server answer406 Not Acceptableor ignore the header and send its default; the contract should say which.Authorization: a scheme and credentials, such asBearer <token>(a token issued at sign-in) orBasic <credentials>(user name and password joined by a colon). Missing or invalid credentials get401 Unauthorized, which RFC 9110 requires to carry aWWW-Authenticateheader. Basic credentials are Base64-encoded, not encrypted: anyone who sees them can read them.Cache-Control: directives for caches (RFC 9111).max-age=600: fresh for 600 seconds, so a cache may reuse it without asking the server.no-cache: may be stored, but must be revalidated before each reuse.no-store: must not be stored.private: only a private cache, such as the browser, may store it. The HTTP caching lesson goes deeper.Set-Cookie: asks the browser to store a cookie, which it returns in theCookieheader of later matching requests. Attributes such asMax-Age,SecureandHttpOnlycontrol how long it lives, where it goes and who can read it; the cookies and sessions lesson tests them.
Text version
PUT /api/v1/me/preferences HTTP/1.1
Host: test.shop.example.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer eyJhbGciOi...
Content-Length: 18
{"language": "ar"}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 55
Cache-Control: no-store
Set-Cookie: lang=ar; Path=/; Max-Age=31536000; Secure
{"language": "ar", "updatedAt": "2026-10-01T09:30:00Z"}What to test in headers
Headers are part of the contract, so they belong in the test basis and the expected results.
- Response checks: the documented
Content-Type, with a body that really is in that format;Locationafter201 Createdif the contract promises it;Cache-Control: no-storeon personal data and credentials; the agreed attributes on everySet-Cookie. - Request tests, one change at a time: no
Authorizationor an expired token (401withWWW-Authenticate); JSON sent asContent-Type: text/plain(415, or the documented400); anAcceptformat the API lacks (406, or the documented default). A duplicated header or a huge value may get400or431 Request Header Fields Too Large; a500or a crash is a defect. - Leaks and security headers:
Server: Apache/2.4.41 (Ubuntu)orX-Powered-By: PHP/7.4.3tell attackers which versions to look up; the OWASP HTTP Headers Cheat Sheet recommends removingX-Powered-Byand removingServeror giving it a non-informative value. Check the agreed security headers, such asStrict-Transport-Security(it tells browsers to reach the site over HTTPS only), only on sites you own or have written permission to test.
Try it: API Header Validator Try it: Security Header Analyzer
Worked example: a profile response
A tester checks the profile endpoint of a shop's web app on the test environment. The status is 200 and the body is right, so the UI test passes, yet the raw exchange holds four header defects, each in its own report because each may be fixed in a different place: application code, session settings or web server configuration.
Content-Type: text/htmlon JSON: clients that follow the header may not parse it as JSON, and a browser renders it as HTML, a script injection risk once a field holds user input. Expected:application/json.Cache-Control: public, max-age=3600on personal data: a shared cache such as a CDN or a company proxy may keep the profile for an hour and serve it to someone else. Expected:no-store.Set-Cookiesets the session cookie again withoutSecureandHttpOnly: it may travel over unencrypted HTTP and page scripts can read it. Expected: the agreed session cookie attributes.ServerandX-Powered-Byreveal exact versions. Expected: removed or generic.- After the fixes, confirmation testing repeats the request and compares the headers, and the four checks join the API regression suite.
GET /api/v1/me HTTP/1.1
Host: test.shop.example.com
Accept: application/json
Cookie: sid=9f2c41d7e0
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 62
Cache-Control: public, max-age=3600
Set-Cookie: sid=9f2c41d7e0; Path=/
Server: Apache/2.4.41 (Ubuntu)
X-Powered-By: PHP/7.4.3
{"id": 58, "name": "Test User", "email": "user58@example.com"}Try it: HTTP Headers Parser Try it: Cache Header Analyzer Try it: Cookie Analyzer
Common mistakes
- Checking the status code and the body but never the response headers.
- Comparing header names case-sensitively in automated tests, so a move to HTTP/2 breaks the suite without any defect.
- Reading
no-cacheas "do not cache": it allows storing with revalidation. The directive that forbids storing isno-store. - Assuming the request holds only the headers you typed: browsers and API clients add
User-Agent,Cookieand others. - Pasting real
AuthorizationorCookievalues into reports or screenshots: anyone who copies them can act as that user. Mask them.
The QA perspective
Headers come up in review, in testing and after fixes. In contract review, ask which headers each endpoint needs and returns, what an unsupported Accept or Content-Type gets, and how long each response may be cached; a silent contract is your first finding. During testing, keep the raw request and response with every API result.
Turn each fixed header defect into a regression check. Ask DevOps what the web server, gateway or CDN adds or strips, because a header can be right in the code and changed on the way. Mask credentials in all evidence.
Practice exercise
Review the headers of an address change
A test shop's mobile app changes the delivery address of an order. The contract says: JSON bodies only; an unsupported Content-Type gets 415 and an unsupported Accept gets 406; Basic authentication over HTTPS; responses with personal data carry Cache-Control: no-store.
Review only the headers below; the response body is correct. List each problem with the expected value and the risk, then write three negative tests that each change one request header. The Mutqan Base64 Decoder decodes the Authorization value.
PATCH /api/v1/orders/88121/address HTTP/1.1
Host: test.shop.example.com
Content-Type: text/plain
Accept: application/xml
Authorization: Basic dGVzdGVyMTpUZXN0LVBhc3MtMTIz
Content-Length: 41
{"city": "Jeddah", "postalCode": "23436"}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 59
Cache-Control: no-cache
Set-Cookie: lastCity=Jeddah; Path=/; Max-Age=31536000
Server: nginx/1.18.0
{"orderId": 88121, "city": "Jeddah", "postalCode": "23436"}- Sort the headers into request and response.
- Compare each
Content-Typewith its body and the contract; checkAccept. - Decode the credentials; check the caching, cookie and information headers.
- Write three negative tests, one request header each.
Show the model answer
Request headers: Host, Content-Type, Accept, Authorization, Content-Length. Response headers: Content-Type, Content-Length, Cache-Control, Set-Cookie, Server. Host, both Content-Length values (41 and 59 bytes) and the response Content-Type are correct.
Content-Type: text/plain on JSON: the app sends the wrong label (expected application/json), and the server accepted it instead of answering 415. Risk: the app breaks as soon as the server enforces the contract. Two defects, two reports.
Accept: application/xml got JSON with 200. RFC 9110 allows either reaction, but this contract chose 406, so the 200 is a defect. Risk: a client that asked for XML cannot read the answer. Also ask why the app asks for XML at all.
The Basic value decodes to tester1:Test-Pass-123: Base64 is not encryption. Risk: anyone who sees the request can sign in as tester1. Send it over HTTPS only, mask it in all evidence, and change a password seen in a shared report.
Cache-Control: no-cache breaks the no-store rule: a cache may still keep the address if it revalidates before reuse. Expected: no-store.
Set-Cookie: lastCity=Jeddah keeps part of the address for a year without Secure, so it may travel over unencrypted HTTP: ask whether it is needed, and if so add Secure. Server: nginx/1.18.0 reveals the version attackers would look up. Expected: removed or generic.
Negative tests, each starting from a valid request (application/json in Content-Type and Accept, valid credentials) and changing one header: (1) no Authorization → 401 with a Basic WWW-Authenticate, address unchanged; (2) the same JSON body with Content-Type: application/xml → 415, address unchanged; (3) Accept: image/png → 406, never 500.
Frequently asked questions
Are header names case-sensitive?
No. RFC 9110 defines field names as case-insensitive, and HTTP/2 and HTTP/3 send them in lowercase. Values are another matter: some are case-insensitive, such as media types and the charset parameter (UTF-8 equals utf-8), and others are not, such as access tokens, cookie values and, unless their documentation says otherwise, custom values.
What is the difference between Accept and Content-Type?
Content-Type describes the body that is in this message; Accept describes what the client wants in the response. A request that sends JSON and wants JSON back carries both: Content-Type: application/json and Accept: application/json. A GET request normally has no body, so it may carry Accept but has no reason to carry Content-Type.
Where can I see the headers of a request?
In the browser, open DevTools, select the request in the Network panel and open its Headers tab, which shows request and response headers separately. An API client shows them in a response tab, curl -i prints the response headers and curl -v prints both directions. The Mutqan Request Header Inspector shows what your browser actually sends.
References
- RFC 9110: HTTP SemanticsThe meaning of HTTP methods, header fields, status codes and content negotiation, independent of the wire version.
- RFC 9111: HTTP CachingHow Cache-Control, Expires, ETag and validators decide whether a response may be stored and reused.
- OWASP Cheat Sheet Series: HTTP HeadersWhich security headers to send (HSTS, CSP, X-Content-Type-Options, frame protection) and which to remove (Server, X-Powered-By).
- HTTP headers — MDNReference for request and response headers: syntax, directives and browser support.
Practise with Mutqan tools
Parse raw HTTP request/response headers into a structured table with explanations.
Inspect the request headers your browser or client sends, with explanations for each.
Validate API response headers for correctness, security and caching best practices.
Validate Content-Type headers and check that a response body actually matches its declared media type.
Analyze Cache-Control, Expires, ETag, Last-Modified, Vary and Age headers and explain how browsers and CDNs will cache the response.
Decode Base64 or Base64url strings to text, with automatic padding repair.
Go deeper
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.