Question

PHP cURL Custom Headers: Sending HTTP Request Headers

phpcurl

Question

How can custom HTTP headers be added to an HTTP request made with PHP cURL? For example, how would a request include these non-standard headers?

X-Apple-Tz: 0
X-Apple-Store-Front: 143444,12

Short Answer

You will learn how HTTP request headers work and how to send custom headers in PHP with CURLOPT_HTTPHEADER. You will also see how to combine custom headers with common headers such as Accept and Authorization, inspect outgoing requests, and avoid replacing headers accidentally.

Concept

HTTP headers are name-and-value pieces of metadata sent alongside an HTTP request. They describe information about the request rather than its main body.

For example, a server may use headers to determine:

  • Which response formats the client accepts (Accept)
  • Whether the request is authenticated (Authorization)
  • Which application or device made the request (User-Agent)
  • A client-specific setting required by an API (X-Apple-Tz)

In PHP cURL, custom request headers are set with CURLOPT_HTTPHEADER. Its value must be an array of strings, with each string formatted exactly as:

Header-Name: header value

The header name and its value are separated by a colon. cURL adds the supplied headers when it sends the request.

Custom headers are common when integrating with APIs. However, a header alone does not grant access to a private service: servers can also require valid credentials, signed requests, cookies, rate-limit compliance, and permission to use the API.

Mental Model

Think of an HTTP request as a package sent to a company.

  • The URL is the delivery address.
  • The request body is the item inside the package.
  • Headers are labels on the outside, such as “Fragile,” “Deliver before noon,” or “Sender account: 123.”

CURLOPT_HTTPHEADER is the step where you attach those labels. Each array item is one label, written as Name: Value.

Take Quiz

Syntax and Examples

Use curl_setopt() with CURLOPT_HTTPHEADER.

<?php

$ch = curl_init('https://example.com/api/artwork');

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-Apple-Tz: 0',
    'X-Apple-Store-Front: 143444,12',
]);

$response = curl_exec($ch);

if ($response === false) {
    throw new RuntimeException(curl_error($ch));
}

curl_close($ch);

echo $response;

The array passed to CURLOPT_HTTPHEADER contains two strings:

  • 'X-Apple-Tz: 0' sends the X-Apple-Tz header with value 0.
  • 'X-Apple-Store-Front: 143444,12' sends the X-Apple-Store-Front header with value 143444,12.

Here is a more typical API request that includes standard and custom headers:

<?php

$ch = curl_init('https://api.example.com/v1/products');

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer your-access-token',
        'X-Client-Timezone: UTC',
    ],
]);

$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

if ($response === false) {
    throw new RuntimeException('cURL failed: ' . curl_error($ch));
}

curl_close($ch);

echo "Status: {$statusCode}\n";
echo $response;

Use CURLOPT_POSTFIELDS separately when you also need to send a request body, such as JSON for a POST request.

Step by Step Execution

Consider this request:

<?php

$ch = curl_init('https://httpbin.org/headers');

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-App-Name: ArtworkTool',
    'X-Apple-Tz: 0',
]);

$response = curl_exec($ch);
curl_close($ch);

echo $response;

Step by step:

  1. curl_init(...) creates a cURL handle configured with the target URL.

  2. CURLOPT_RETURNTRANSFER tells cURL to return the response as a string instead of printing it immediately.

  3. CURLOPT_HTTPHEADER receives an array of header lines.

  4. When curl_exec($ch) runs, cURL makes the HTTP request and includes:

    X-App-Name: ArtworkTool
    X-Apple-Tz: 0
    
  5. The /headers endpoint returns a JSON response containing the headers it received.

  6. curl_close($ch) releases the cURL handle.

A header-inspection endpoint such as https://httpbin.org/headers is useful for verifying header formatting during development.

Real World Use Cases

Custom headers are used in many kinds of PHP integrations:

  • API authentication: Send Authorization: Bearer ... or an API key header required by a service.
  • JSON APIs: Send Accept: application/json to state that the client expects JSON.
  • Content submission: Send Content-Type: application/json when posting JSON data.
  • Client identification: Send a User-Agent or application-specific X-Client-Version header.
  • Tracing requests: Send a correlation ID such as X-Request-ID so logs across multiple services can be connected.
  • Feature controls: Send a header such as X-Feature-Preview: enabled when an internal API supports an approved preview mode.
  • Localization: Send a locale or time zone header when an API uses it to format dates, currencies, or translated content.

Always use a service's documented API and authentication process. Headers observed from another client may be internal, change without notice, or be insufficient for authorized access.

Take Quiz

Real Codebase Usage

In production code, developers usually build headers from request context and add only the headers required by the endpoint.

Build a reusable request function

<?php

function getJson(string $url, string $token): array
{
    $ch = curl_init($url);

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_HTTPHEADER => [
            'Accept: application/json',
            'Authorization: Bearer ' . $token,
            'X-Request-ID: ' . bin2hex(random_bytes(8)),
        ],
    ]);

    $body = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

    if ($body === false) {
        $error = curl_error($ch);
        curl_close($ch);
        throw new RuntimeException("Network error: {$error}");
    }

    curl_close($ch);

    if ($status < 200 || $status >= 300) {
        throw new RuntimeException("Unexpected HTTP status: {$status}");
    }

    return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}

This pattern:

  • Uses curl_setopt_array() to group related options.
  • Sets a timeout so a stalled network request does not wait forever.
  • Uses an early failure check after curl_exec().
  • Checks the HTTP status code, because a request can succeed at the network level but still receive 401, 404, or 500.
  • Keeps secrets such as tokens out of source code by accepting them as configuration.

Add JSON request data

When sending JSON, both the body and the matching Content-Type header are needed:

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode(['name' => 'Album']),
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Content-Type: application/json',
    ],
]);

Common Mistakes

Passing one string instead of an array

CURLOPT_HTTPHEADER expects an array.

// Incorrect
curl_setopt($ch, CURLOPT_HTTPHEADER, 'X-Apple-Tz: 0');
// Correct
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Apple-Tz: 0']);

Omitting the colon

A header must have the Name: Value format.

// Incorrect
'X-Apple-Tz 0'
// Correct
'X-Apple-Tz: 0'

Including Copy from copied documentation

Some web pages show a “Copy” control beside sample headers. Copy is not part of the actual header name.

// Incorrect: sends a header literally named CopyX-Apple-Tz
'CopyX-Apple-Tz: 0'
// Correct
'X-Apple-Tz: 0'

Calling CURLOPT_HTTPHEADER twice unintentionally

The later call replaces the earlier header list; it does not append to it.

// The first list is replaced.
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: application/json']);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-App-Name: Demo']);

Put all headers in one array, or merge arrays before setting the option.

$headers = array_merge(
    ['Accept: application/json'],
    ['X-App-Name: Demo']
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

Using Content-Type when no body is being sent

Content-Type describes the format of the request body. For a simple GET request with no body, it is often unnecessary. Use Accept when you want to state the format you expect in the response.

Exposing secrets in logs or source code

Do not commit API keys or bearer tokens. Also avoid logging full header arrays because they can contain credentials.

Ignoring cURL errors and HTTP status codes

curl_exec() returning a string does not guarantee that the API accepted the request. Check both curl_error() and the response status code.

Comparisons

ItemPurposeExample
CURLOPT_HTTPHEADERSets request headers as an array of Name: Value strings['Accept: application/json']
CURLOPT_POSTFIELDSSets the request body datajson_encode(['name' => 'Album'])
Accept headerSays what response format the client prefersAccept: application/json
Content-Type headerSays what format the request body usesContent-Type: application/json
CURLOPT_USERAGENTConvenient option for the User-Agent headerCURLOPT_USERAGENT => 'MyApp/1.0'
CURLOPT_HTTPGET / CURLOPT_POSTControls or indicates the HTTP methodCURLOPT_POST => true

CURLOPT_USERAGENT is a dedicated shortcut for one common header. For most other headers, including custom X-... headers, use CURLOPT_HTTPHEADER.

Header names are case-insensitive in HTTP, so x-app-name and X-App-Name represent the same header. Use conventional capitalization to keep code readable.

Take Quiz

Cheat Sheet

curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Header-Name: value',
    'Another-Header: another value',
]);
  • CURLOPT_HTTPHEADER requires an array.
  • Each item is one complete header line: 'Name: Value'.
  • Do not include UI text such as Copy in the header name.
  • Set all headers in one call; a new call replaces the prior list.
  • Use Accept: application/json when expecting JSON.
  • Use Content-Type: application/json when sending a JSON body.
  • Check cURL failures with curl_error($ch).
  • Check server responses with curl_getinfo($ch, CURLINFO_RESPONSE_CODE).
  • Use CURLOPT_VERBOSE during local debugging to inspect request details; do not expose sensitive logs.
curl_setopt($ch, CURLOPT_VERBOSE, true);

Example custom headers:

[
    'X-Apple-Tz: 0',
    'X-Apple-Store-Front: 143444,12',
]

FAQ

How do I add custom headers to a PHP cURL request?

Pass an array of Header-Name: value strings to CURLOPT_HTTPHEADER.

curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-App-Name: MyApp']);

Can I send multiple headers with PHP cURL?

Yes. Add each header as a separate array item.

curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Accept: application/json',
    'X-App-Name: MyApp',
]);

Does PHP cURL add headers or replace them?

When you set CURLOPT_HTTPHEADER again, the new array replaces the header list previously set with that option. Build one complete array before calling it.

What is the difference between Accept and Content-Type?

Accept describes the response format you want. Content-Type describes the format of the request body you are sending.

Do custom headers need an X- prefix?

No. The X- prefix is a historical convention, not a requirement. Use the exact header names defined by the API you are calling.

Can a custom header authenticate me to an API?

Only if the API explicitly uses that header as part of its documented authentication scheme. A header copied from another application usually does not replace valid credentials or authorization.

How can I verify that cURL sent my headers?

Use a request-inspection endpoint in a safe test environment, such as https://httpbin.org/headers, or enable CURLOPT_VERBOSE while ensuring logs do not reveal secrets.

Take Quiz

Mini Project

Description

Create a small PHP command-line client that sends custom headers to a test endpoint and prints the headers that the server received. This is a safe way to practice correct header formatting and verify your cURL configuration.

Goal

Send two custom headers with PHP cURL and confirm that the server receives them.

Requirements

Use PHP cURL to make a GET request to https://httpbin.org/headers.

Set X-App-Name to HeaderPractice.

Set X-Client-Timezone to UTC.

Return the response as a string instead of printing it directly.

Handle a cURL execution error.

Decode and display the JSON response.

Take Quiz

Keep learning

Related questions

View more