PHP HTTP Integration
PHP's cURL extension can call the Telemetry HTTP API without an additional client package. Keep the API key in server-side configuration and use explicit connection and request timeouts.
Configure and send an event
<?php
$apiKey = getenv("TELEMETRY_API_KEY");
if (!is_string($apiKey) || $apiKey === "") {
throw new RuntimeException("TELEMETRY_API_KEY is not configured");
}
$payload = [
"table" => "api_request_completed",
"data" => [
"event_id" => "evt_request_101",
"route_template" => "/api/projects/:id",
"method" => "GET",
"status_code" => 200,
"status" => "success",
"latency_ms" => 184,
],
];
$request = curl_init("https://api.telemetry.sh/log");
curl_setopt_array($request, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT_MS => 2_000,
CURLOPT_TIMEOUT_MS => 10_000,
CURLOPT_HTTPHEADER => [
"Authorization: " . $apiKey,
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
]);
$body = curl_exec($request);
$curlError = curl_error($request);
$status = curl_getinfo($request, CURLINFO_HTTP_CODE);
curl_close($request);
if ($body === false || $status < 200 || $status >= 300) {
throw new RuntimeException(
"Telemetry log failed with HTTP " . $status .
($curlError !== "" ? " and a transport error" : "")
);
}
$response = json_decode($body, true, flags: JSON_THROW_ON_ERROR);
Telemetry adds timestamp_utc. Do not send credentials, authorization headers, cookies, request input, exception text, or private customer content. Use a write-scoped key for ingestion.
Send a batch
The data value may be an array of compatible rows:
$payload = [
"table" => "job_completed",
"data" => [
[
"event_id" => "evt_job_101",
"job_name" => "invoice_sync",
"status" => "success",
"duration_ms" => 912,
],
[
"event_id" => "evt_job_102",
"job_name" => "invoice_sync",
"status" => "failed",
"duration_ms" => 2401,
"error_type" => "provider_timeout",
],
],
];
Keep batches bounded and schema-compatible. If the application queues events, define maximum depth, maximum age, overflow behavior, retry budget, and shutdown handling.
Run a query
Use a read-scoped key:
<?php
$sql = <<<'SQL'
SELECT
route_template,
COUNT(*) AS requests,
ROUND(AVG(latency_ms), 0) AS avg_latency_ms
FROM api_request_completed
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
GROUP BY route_template
ORDER BY requests DESC;
SQL;
$request = curl_init("https://api.telemetry.sh/query");
curl_setopt_array($request, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT_MS => 2_000,
CURLOPT_TIMEOUT_MS => 30_000,
CURLOPT_HTTPHEADER => [
"Authorization: " . $apiKey,
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode(["query" => $sql], JSON_THROW_ON_ERROR),
]);
$body = curl_exec($request);
$status = curl_getinfo($request, CURLINFO_HTTP_CODE);
curl_close($request);
if ($body === false || $status < 200 || $status >= 300) {
throw new RuntimeException("Telemetry query failed with HTTP " . $status);
}
$results = json_decode($body, true, flags: JSON_THROW_ON_ERROR);
foreach ($results["data"] ?? [] as $row) {
// Validate expected keys and nulls before using the row.
}
Use the asynchronous Query API for large JSON or Parquet exports.
Retry and failure policy
A timeout is ambiguous: the server may have accepted the event before the response was lost. Retry only transient connection failures, 429, 502, 503, and 504. Reuse event_id, apply backoff with jitter, and cap attempts. Do not retry an unchanged 400.
For ordinary analytics, telemetry failure should not replace a completed customer response. Use an application-owned durable outbox for billing or approved audit events that cannot be dropped.
Verify and troubleshoot
Query the newest rows and inspect the schema through GET /tables/<table>/schema. Exercise success, failure, retry, and timeout branches.
- Empty API key: validate server-side configuration before constructing the request.
curl_execreturnsfalse: recordcurl_errnoand a controlled error category, not the key or payload.401or403: replace the key or correct its scope.400: inspect table naming, JSON shape, and type compatibility.- Process shutdown: direct HTTP calls have no background queue to flush; track required requests or persist events first.
See the Laravel integration, Log API, rate limits, and batching guide.