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

MethodGood fitConsiderations
Session cookieSame-site browser applicationUse CSRF protection for state changes.
Opaque API tokenSimple integrations and service accountsStore a hash, support expiration and revocation.
OAuth 2.0 / OpenID ConnectThird-party authorization or centralized identityUse a mature library or identity provider.
Signed webhookInbound event deliveryVerify 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.