Question
I am building a payment interface that sends a JSON POST request to my PHP endpoint, but I cannot decode the received data. When I try to print $_POST:
<?php
echo $_POST;
PHP outputs:
Array
The payment provider's logs indicate that the request was sent successfully. Why does this happen, and how can I read and decode the JSON request body in PHP?
Short Answer
You will learn the difference between form POST fields and a JSON request body in PHP. You will be able to read JSON from php://input, decode it safely, diagnose invalid payloads, and apply the pattern to payment webhooks and APIs.
Concept
$_POST is a PHP superglobal containing parsed form fields. PHP fills it automatically when a request uses a form-oriented content type such as:
application/x-www-form-urlencodedmultipart/form-data
A JSON API or webhook usually sends this header instead:
Content-Type: application/json
In that case, the data is sent as the raw HTTP request body. PHP does not automatically turn JSON into $_POST, so you must read the body yourself and decode it.
$rawBody = file_get_contents('php://input');
$data = json_decode($rawBody, true);
php://input is a read-only PHP stream that provides the raw request body. json_decode() converts the JSON text into a PHP value.
This matters because many modern systems communicate with JSON: REST APIs, JavaScript front ends, mobile apps, and payment-provider webhooks. Reading the correct input source is the first step before validation and business logic.
Mental Model
Think of an HTTP request as a delivery package:
- The headers are the label describing the package contents.
- The body is the item inside the package.
$_POSTis a PHP assistant trained to unpack HTML form packages.- JSON is a different kind of package, so the assistant leaves it intact in
php://input.
You read the package yourself, then use json_decode() as a translator from JSON text into PHP arrays and values.
Syntax and Examples
Read the raw body, decode it, and check whether decoding succeeded:
<?php
header('Content-Type: application/json');
$rawBody = file_get_contents('php://input');
$data = json_decode($rawBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
echo json_encode([
'error' => 'Invalid JSON: ' . json_last_error_msg(),
]);
exit;
}
$name = $data['name'] ?? 'Guest';
echo json_encode([
'message' => "Hello, {$name}",
]);
If the client sends:
{
"name": "Amina",
"amount": 49.99
}
then $data becomes this PHP associative array:
[
'name' => 'Amina',
'amount' => 49.99,
]
The second argument, true, tells json_decode() to create associative arrays. Without it, JSON objects become PHP stdClass objects and properties are accessed with -> instead of array brackets.
Step by Step Execution
Consider this endpoint:
<?php
$rawBody = file_get_contents('php://input');
$data = json_decode($rawBody, true);
$status = $data['status'] ?? 'unknown';
echo $status;
Given this request body:
{"status":"paid","invoice_id":"INV-1042"}
Execution proceeds as follows:
-
file_get_contents('php://input')reads the exact request body as a string.$rawBody === '{"status":"paid","invoice_id":"INV-1042"}' -
json_decode($rawBody, true)parses that JSON string into an associative array.$data === [ 'status' => 'paid', 'invoice_id' => 'INV-1042', ] -
$data['status'] ?? 'unknown'gets the status when present. If it is missing, it safely uses'unknown'. -
echo $statusoutputs:paid
For debugging, use var_dump($data) or print_r($data, true). Do not use echo $_POST, because an array cannot be displayed as ordinary text.
Real World Use Cases
Common places where PHP receives JSON request bodies include:
- Payment webhooks: A provider notifies your server that an invoice was paid, refunded, or failed.
- REST API endpoints: A mobile app sends profile updates such as
{"email":"user@example.com"}. - JavaScript front ends:
fetch()sends JSON to a PHP backend. - Internal services: One service posts order or inventory events to another.
- Command-line integrations: Automation tools call an HTTP endpoint with JSON configuration or event data.
For a payment webhook, decoding JSON is only one part of the task. You should also verify the provider's signature, validate expected fields, and make processing safe to repeat because providers may resend events.
Real Codebase Usage
A production endpoint normally separates input handling from payment logic and rejects invalid requests early.
<?php
function readJsonBody(): array
{
$rawBody = file_get_contents('php://input');
try {
$data = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $exception) {
http_response_code(400);
echo json_encode(['error' => 'Request body must contain valid JSON.']);
exit;
}
if (!is_array($data)) {
http_response_code(400);
echo json_encode(['error' => 'JSON body must be an object.']);
exit;
}
return $data;
}
$event = readJsonBody();
$eventType = $event['type'] ?? null;
if ($eventType === null) {
http_response_code(422);
echo json_encode(['error' => 'Missing event type.']);
exit;
}
// Verify the payment provider signature using the raw body before trusting $event.
// Process the event only after verification.
http_response_code(200);
echo json_encode(['received' => true]);
Useful project patterns:
- Guard clauses: Stop immediately when JSON is invalid or required data is absent.
- Validation: Check types and allowed values before using values in database queries or payment logic.
- Raw-body preservation: Keep
$rawBodyfor webhook signature verification. A signature is commonly calculated from the original bytes, not from re-encoded PHP data. - Clear responses: Return appropriate HTTP status codes such as
400for malformed JSON and422for valid JSON with invalid fields. - Idempotency: Store processed event IDs so a retry does not charge or update an order twice.
Common Mistakes
Echoing an array
This produces the word Array and may emit an "Array to string conversion" warning:
// Incorrect for inspecting array contents
echo $_POST;
Use one of these while debugging:
var_dump($_POST);
print_r($_POST);
echo json_encode($_POST, JSON_PRETTY_PRINT);
Expecting JSON in $_POST
// Usually null when Content-Type is application/json
$amount = $_POST['amount'];
Instead, decode the raw body:
$data = json_decode(file_get_contents('php://input'), true);
$amount = $data['amount'] ?? null;
Skipping JSON error handling
$data = json_decode($rawBody, true);
// $data can be null because the JSON is invalid.
Check json_last_error() or use JSON_THROW_ON_ERROR.
Reading php://input too late or repeatedly
Read the body once near the start of the endpoint and store it in $rawBody. This is especially important when the same raw text is needed for webhook signature verification.
Trusting a payment callback without verification
A request reaching your endpoint does not prove it came from the payment provider. Follow that provider's documentation to verify its signature or secret, and validate the event before changing an order status.
Assuming a successful provider log means PHP decoded the data
A provider log can confirm delivery, but PHP still needs the correct Content-Type, raw-body reading, valid JSON, and endpoint configuration to process the request.
Comparisons
| Request format | Typical Content-Type | Where PHP reads it | Example access |
|---|---|---|---|
| URL-encoded form | application/x-www-form-urlencoded | $_POST | $_POST['email'] |
| File upload form | multipart/form-data | $_POST and $_FILES | $_FILES['avatar'] |
| JSON API or webhook | application/json | php://input, then json_decode() | $data['email'] |
| Query string | Part of the URL | $_GET | $_GET['page'] |
json_decode() also has two common output styles:
| Decode call | JSON object becomes | Access syntax |
|---|---|---|
json_decode($json, true) | Associative array | $data['status'] |
json_decode($json) | stdClass object | $data->status |
Both are valid. Associative arrays are often convenient for request validation, but choose one style and use it consistently.
Cheat Sheet
// Read the JSON request body once
$rawBody = file_get_contents('php://input');
// Decode to an associative array
$data = json_decode($rawBody, true);
// Check for parsing errors
if (json_last_error() !== JSON_ERROR_NONE) {
$message = json_last_error_msg();
}
// Or throw an exception on invalid JSON
$data = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
// Safely read an optional field
$email = $data['email'] ?? null;
// Debug an array
var_dump($data);
print_r($data);
// Return JSON
header('Content-Type: application/json');
echo json_encode(['ok' => true]);
Rules to remember:
- JSON sent with
Content-Type: application/jsonis normally not placed in$_POST. echocannot meaningfully display an array; usevar_dump()orprint_r().- Validate decoded data before using it.
- Keep the original raw body when verifying webhook signatures.
- Never rely on a
Content-Typeheader alone as a security check.
FAQ
Why does echo $_POST print Array in PHP?
$_POST is an array. echo outputs strings, not an array's contents. Use var_dump($_POST) or print_r($_POST) to inspect it.
Why is $_POST empty when I send JSON?
PHP automatically populates $_POST for form-encoded requests, not for a JSON body with Content-Type: application/json. Read php://input and decode it.
How do I read JSON POST data in PHP?
$data = json_decode(file_get_contents('php://input'), true);
Then check for JSON errors before relying on $data.
Should I use $_REQUEST for JSON data?
No. $_REQUEST combines query, form, and cookie values based on configuration; it does not solve JSON body parsing and can make input sources unclear.
What does the true argument in json_decode() do?
It converts JSON objects into associative arrays. Without true, JSON objects become stdClass objects.
How can I tell whether the sender provided invalid JSON?
Use json_last_error() and json_last_error_msg(), or use JSON_THROW_ON_ERROR and catch JsonException.
Is decoding JSON enough for a payment webhook?
No. Also verify the payment provider's signature, validate fields, handle duplicate events safely, and use HTTPS.
Mini Project
Description
Build a small PHP endpoint that accepts a JSON payment event. It validates the incoming JSON, checks the required fields, and returns a JSON response. This mirrors the first stage of receiving a payment-provider webhook.
Goal
Create an endpoint that safely reads a JSON payment event and reports whether the event is accepted.
Requirements
- Read the incoming request body from
php://input. - Reject malformed JSON with HTTP status
400. - Require
event_id,type, andamountfields. - Reject missing or invalid fields with HTTP status
422. - Return a JSON success response for a valid event.
- Do not treat the endpoint as secure payment verification; signature verification must be added for a real provider.
Keep learning
Related questions
Are PDO Prepared Statements Enough to Prevent SQL Injection in PHP?
Learn how PDO prepared statements prevent SQL injection in PHP, what they protect, and the mistakes that still leave MySQL apps vulnerable.
Can You Bind an Array to an IN Clause in PHP PDO?
Learn how PDO handles placeholders in IN() clauses, why arrays cannot be bound directly, and the safe PHP pattern to build dynamic queries.
Choosing the Right MySQL Collation for PHP and UTF-8
Learn how MySQL character sets and collations work with PHP, and how to choose a practical UTF-8 setup for web applications.