Building JSON APIs with PHP
A useful API has predictable resource names, validation, status codes, authentication, pagination, idempotency, and errors that clients can act on.
Read and Validate a JSON Request
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
try {
$payload = json_decode(
file_get_contents('php://input'),
true,
512,
JSON_THROW_ON_ERROR
);
} catch (JsonException) {
http_response_code(400);
echo json_encode(['error' => ['code' => 'invalid_json', 'message' => 'Malformed JSON.']]);
exit;
}
$name = trim((string) ($payload['name'] ?? ''));
if ($name === '') {
http_response_code(422);
echo json_encode(['error' => ['code' => 'validation_failed', 'fields' => ['name' => 'Name is required.']]]);
exit;
}
Return a Created Resource
<?php
http_response_code(201);
header('Location: /api/v1/customers/' . $customerId);
echo json_encode([
'data' => [
'id' => $customerId,
'name' => $name,
'status' => 'active',
],
], JSON_THROW_ON_ERROR);
Consistent Error Shape
{
"error": {
"code": "trip_not_ready",
"message": "The trip cannot start until a vehicle and driver are assigned.",
"request_id": "req_7c761f23"
}
}
Expose a stable machine-readable code and a safe human-readable message. Keep stack traces, SQL, credentials, and internal hostnames in server logs.
Authentication Options
| Method | Good fit | Considerations |
|---|---|---|
| Session cookie | Same-site browser application | Use CSRF protection for state changes. |
| Opaque API token | Simple integrations and service accounts | Store a hash, support expiration and revocation. |
| OAuth 2.0 / OpenID Connect | Third-party authorization or centralized identity | Use a mature library or identity provider. |
| Signed webhook | Inbound event delivery | Verify timestamp and signature over the raw body. |
Webhook Signature Verification
<?php
declare(strict_types=1);
$rawBody = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $_ENV['WEBHOOK_SECRET']);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
Operational API Features
- Version intentionally and document compatibility.
- Use idempotency keys for retryable create/payment operations.
- Paginate collections and set maximum page sizes.
- Apply per-identity and per-IP rate limits.
- Return request IDs and log execution duration.
- Set timeouts for every outbound HTTP request.
- Queue slow or unreliable work instead of holding the request open.