Adding a Custom Header or Footer in C# with HttpClient
Learn where every HttpClient header belongs, how to add headers safely, and what “footer” can mean when you need HTTP trailers.

In C#, add a header to every request through HttpClient.DefaultRequestHeaders, add a header to one request through HttpRequestMessage.Headers, and put metadata about a request body, such as Content-Type, on HttpContent.Headers. There is no standard “footer” collection on HttpClient. If you mean an HTTP trailer, treat it as a protocol feature that depends on the runtime, handler, HTTP version, and server, and verify support before relying on it.
This guide explains each placement, shows complete runnable C# examples, covers reusable handlers, concurrency, authentication, content headers, HTTP trailers, diagnostics, and common failures. It also shows how to capture the resulting endpoint with ScreenshotNeo when you need a clean visual record of an API response or documentation page.
1. Choose the header location by scope and meaning
| What you need | Use | Example |
|---|---|---|
| Same metadata on requests from one client | DefaultRequestHeaders |
Authorization, API version, stable user agent |
| Metadata for one outgoing message | HttpRequestMessage.Headers |
Request ID, conditional request, one-off feature flag |
| Metadata describing the body | HttpContent.Headers |
Content-Type, content length, content encoding |
| Reusable cross-cutting behavior | DelegatingHandler |
Correlation IDs, signing, logging, retry-aware decoration |
| Data sent after the main body | HTTP trailers, if supported | Protocol-specific integrity or digest metadata |
Microsoft documents DefaultRequestHeaders as the collection sent with each request made by that client. It also warns: “DefaultRequestHeaders should not be modified while there are outstanding requests.” Configure stable defaults before concurrent work begins. See the Microsoft API reference.

2. Add a header to every request from one HttpClient
Use this pattern for values that are stable for the lifetime of a client instance. A common example is a bearer token, but the same approach works for an API version or a product-specific request header.
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
public static class Program
{
public static async Task Main()
{
var accessToken = Environment.GetEnvironmentVariable("API_ACCESS_TOKEN")
?? throw new InvalidOperationException("Set API_ACCESS_TOKEN first.");
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Add("X-Api-Version", "2026-01");
using var response = await client.GetAsync("https://api.example.com/items");
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
}
}
Set defaults before calling GetAsync, SendAsync, or another operation. Do not mutate the defaults for each request on a shared client. If a token changes, create a request-specific authorization header or use a handler that reads the current token for each send.
Typed values versus Add
Use typed properties when the framework provides them. For example, Authorization accepts an AuthenticationHeaderValue, which avoids malformed schemes. Add is appropriate for custom names such as X-Request-Id. Header names are case-insensitive on the wire, but use one spelling consistently in your code and logs.
3. Add a header to one request
Use HttpRequestMessage.Headers when the value belongs to one operation. Microsoft documents this collection on HttpRequestMessage; it keeps request metadata separate from body metadata.
using System;
using System.Net.Http;
using System.Threading.Tasks;
public static class Program
{
public static async Task Main()
{
using var client = new HttpClient();
using var request = new HttpRequestMessage(
HttpMethod.Get,
"https://api.example.com/items");
request.Headers.Add("X-Request-Id", Guid.NewGuid().ToString("N"));
request.Headers.Add("X-Feature-Preview", "true");
using var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
}
This is also the right place for conditional headers:
using var request = new HttpRequestMessage(
HttpMethod.Get,
"https://api.example.com/items");
request.Headers.IfNoneMatch.ParseAdd("\"catalog-v17\"");
using var response = await client.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.NotModified)
{
Console.WriteLine("Use the cached representation.");
}
4. Put Content-Type and other body metadata on HttpContent
Content-Type describes the representation in the request body. Attach it to the content object, not to HttpRequestMessage.Headers. The HttpContentHeaders API exposes the content-header collection and its ContentType property.
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
var json = "{\"name\":\"Ada\"}";
using var content = new StringContent(json, Encoding.UTF8);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
using var response = await client.PostAsync(
"https://api.example.com/items",
content);
response.EnsureSuccessStatusCode();
For a simpler JSON call in modern .NET, PostAsJsonAsync sets the JSON media type for you. Explicitly setting ContentType is useful when the server requires a parameter such as a profile or a vendor media type.
Request headers and content headers are different collections
Headers such as Accept, Authorization, If-Match, and custom routing metadata belong to the request. Headers such as Content-Type, Content-Encoding, and Content-Length describe the body. Trying to add a content header to the request collection can produce an InvalidOperationException or an invalid wire message.
5. Centralize cross-cutting headers with DelegatingHandler
A handler is useful when a rule applies to many clients or must be evaluated for every send. The .NET HTTP namespace includes DelegatingHandler for composing handler chains. This is a better fit than repeatedly editing DefaultRequestHeaders when the value is dynamic.
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public sealed class CorrelationHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-Correlation-Id"))
{
request.Headers.Add("X-Correlation-Id", Guid.NewGuid().ToString("N"));
}
return base.SendAsync(request, cancellationToken);
}
}
var handler = new CorrelationHandler
{
InnerHandler = new HttpClientHandler()
};
using var client = new HttpClient(handler);
using var response = await client.GetAsync("https://api.example.com/items");
Keep handlers small and deterministic. If a handler adds authentication, make sure retries cannot accidentally reuse an expired signature or nonce. If you use dependency injection, register an IHttpClientFactory client and attach the handler through the client configuration.
6. What “footer” can mean: HTTP trailers
HTTP has a concept called trailers: metadata transmitted after the message body, usually with a streaming or chunked response. That is different from an HTML footer, a PDF footer, or a second ordinary header block. The Microsoft documentation reviewed for this guide does not define a standard HttpClient “footer” API, and support details vary by .NET runtime, HTTP version, handler, and server.
Before implementing trailers, answer these questions:
- Does the server advertise and emit trailers for the protocol version you will use?
- Does your target .NET runtime expose the trailer APIs you plan to call?
- Will a proxy, load balancer, or gateway preserve them?
- Can the consumer fall back when trailers are absent?
Do not send a trailer as a normal request header or assume that a response trailer is available before the body has been consumed. Verify the exact API in the documentation for your target framework and test through the same intermediaries used in production. For many APIs, putting a digest or status value in the regular response headers or JSON body is more portable.
7. Complete request examples
GET with a per-request header
using var request = new HttpRequestMessage(
HttpMethod.Get,
"https://api.example.com/items?limit=20");
request.Headers.Accept.ParseAdd("application/json");
request.Headers.Add("X-Request-Id", "demo-123");
using var response = await client.SendAsync(request);
var text = await response.Content.ReadAsStringAsync();
Console.WriteLine($"{(int)response.StatusCode}: {text}");
POST with JSON and a request header
using System.Net.Http.Json;
using var request = new HttpRequestMessage(
HttpMethod.Post,
"https://api.example.com/items")
{
Content = JsonContent.Create(new { name = "Ada", enabled = true })
};
request.Headers.Add("X-Request-Id", "create-123");
using var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
8. Troubleshooting checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| “Misused header name” exception | A content header was added to request headers, or vice versa. | Move Content-Type and other body metadata to request.Content.Headers. |
| Header appears on the wrong requests | A shared client default was used for a one-off value. | Use HttpRequestMessage.Headers for that request. |
| Authorization changes are ignored | Defaults were mutated while requests were active, or a handler overwrote them. | Set stable defaults once, or calculate the value in a handler/per-request message. |
| Server rejects a custom header | Name, casing convention, value format, or required prefix is wrong. | Compare the server contract and inspect the outgoing request with logging or a proxy. |
| Header is missing after a redirect | The handler may remove sensitive headers when changing hosts. | Avoid cross-host redirects or reapply safe headers deliberately after validating the destination. |
| Trailer never arrives | Runtime, protocol, proxy, or server does not support or preserve it. | Verify support end to end and provide a regular-header or body fallback. |
| Requests hang while reading | The server streams indefinitely or the timeout is too long. | Use a cancellation token, an explicit timeout policy, and HttpCompletionOption.ResponseHeadersRead for streaming scenarios. |
9. Reliability, performance, and security
- Reuse clients appropriately. Reusing a client or an
IHttpClientFactory-managed client avoids unnecessary connection setup. Do not create a new client for every small request unless your architecture specifically requires isolated handlers. - Keep defaults immutable during work. Configure
DefaultRequestHeadersbefore issuing concurrent requests, following Microsoft’s documented warning. - Use cancellation. Pass a cancellation token to
SendAsyncand set a timeout that matches the endpoint’s expected latency. - Protect credentials. Never hard-code bearer tokens in source control or log complete authorization headers. Be careful with redirects and diagnostic dumps.
- Retry only safe operations. A retry policy should understand idempotency, status codes, backoff, and whether a request ID or idempotency key must stay constant.
- Measure at the boundary. Log method, host, status, duration, and a redacted set of headers. Avoid logging secrets and personal data.
10. Or skip the browser setup
If your goal is to capture an endpoint, documentation page, or rendered result rather than build a browser pipeline yourself, ScreenshotNeo provides a single screenshot API request. Its clean-shot pipeline accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture.

cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
C# with HttpClient:
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
public static class Program
{
public static async Task Main()
{
using var client = new HttpClient
{
Timeout = TimeSpan.FromSeconds(90)
};
var url = "https://api.screenshotneo.com/v1/shot" +
"?access_key=YOUR_API_KEY" +
"&url=" + Uri.EscapeDataString("https://stripe.com");
using var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
await using var output = File.Create("shot.webp");
await response.Content.CopyToAsync(output);
}
}
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo API documentation for the complete option list. You can set full-page capture, CSS selectors, dark mode, device presets, viewport and retina scale, PDF output, custom CSS and JavaScript, click and wait actions, blocked resources, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, caching TTLs, signed links, asynchronous jobs, webhooks, bulk capture, and usage reporting.
Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response reports the result through X-Page-Verdict and X-Billed headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots.
Create your free ScreenshotNeo account and start with 1,000 screenshots a month at no charge.
11. FAQ
How do I add a header to every HttpClient request?
Set client.DefaultRequestHeaders before sending requests. Keep the collection unchanged while requests are outstanding.
How do I add a header to one request?
Create an HttpRequestMessage and add it to request.Headers, then send it with SendAsync.
Where does Content-Type go?
On the content object: request.Content.Headers.ContentType. It describes the body, so it is not a general request header.
Can I add an HTML footer with HttpClient?
HttpClient sends HTTP messages; it does not generate document footers. Add an HTML footer in the document or template you download or upload. If you mean HTTP trailers, verify runtime and protocol support first.
Should I use a handler or DefaultRequestHeaders?
Use defaults for stable client-wide values. Use a DelegatingHandler when the value is dynamic or the behavior needs to be shared and composed across clients.
12. Final decision checklist
- Is the value needed on every request from this client? Configure
DefaultRequestHeaders. - Is it needed once? Use
HttpRequestMessage.Headers. - Does it describe the body? Use
HttpContent.Headers. - Does it change per send or require reusable logic? Add a
DelegatingHandler. - Does “footer” mean trailers? Confirm exact runtime, HTTP version, server, and proxy behavior.
- Do you need a clean rendered capture? Use ScreenshotNeo and inspect its verdict and billing headers.