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.
These examples send fictional values to check delivery. Keep them in the reserved telemetry_quickstart sample table; they do not instrument a running application. For real use, record observed values after an actual operation in its own workflow table, and measure duration around that operation rather than the telemetry call.
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" => "telemetry_quickstart",
"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 an ingest-only key for ingestion.
Send a batch
The data value may be an array of compatible rows:
$payload = [
"table" => "telemetry_quickstart",
"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 telemetry_quickstart
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
AND route_template IS NOT NULL
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.