Copy-paste starting points. Set your secrets as environment variables — never hardcode them.
Verifies the signature, rejects replays, acknowledges quickly and processes idempotently — the four things every receiver should do.
<?php
// webhook.php — OmniHealth webhook receiver (verify signature, be idempotent).
$SECRET = getenv('OMNIHEALTH_WEBHOOK_SECRET'); // from your workspace
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_OMNIHEALTH_SIGNATURE'] ?? '';
// 1. Parse "t=<ts>,v1=<hmac>" and verify HMAC-SHA256 of "<ts>.<raw>".
if (!preg_match('/t=(\d+),v1=([a-f0-9]+)/', $sig, $m)) { http_response_code(400); exit; }
[$all, $ts, $mac] = $m;
$expected = hash_hmac('sha256', $ts . '.' . $raw, $SECRET);
if (!hash_equals($expected, $mac)) { http_response_code(400); exit; }
// 2. Reject old timestamps (replay protection).
if (abs(time() - (int)$ts) > 300) { http_response_code(400); exit; }
// 3. Acknowledge fast, then process idempotently.
$event = json_decode($raw, true);
$key = $event['event'] . ':' . ($event['data']['patient_id'] ?? '') . ':' . $event['created_at'];
if (already_processed($key)) { http_response_code(200); exit; } // your own store
switch ($event['event']) {
case 'service.enabled': start_sync($event['data']['patient_id']); break;
case 'service.disabled': stop_sync($event['data']['patient_id']); break;
case 'observation.created': /* react to new data */ break;
}
mark_processed($key);
http_response_code(200);
echo 'ok';express.raw(), not express.json().Fetch a token once and reuse it until just before it expires.
<?php
// A tiny cached-token helper. Fetch once, reuse until it expires.
function omnihealth_token(): string {
static $tok = null, $exp = 0;
if ($tok && time() < $exp - 30) return $tok;
$r = json_decode(file_get_contents('https://omnihealth.solutions/fhir/token', false, stream_context_create([
'http' => ['method' => 'POST', 'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query([
'grant_type' => 'client_credentials',
'client_id' => getenv('OMNIHEALTH_CLIENT_ID'),
'client_secret' => getenv('OMNIHEALTH_CLIENT_SECRET'),
'scope' => 'system/Observation.write system/Notification.write',
])]]])), true);
$tok = $r['access_token']; $exp = time() + (int)$r['expires_in'];
return $tok;
}
Import the OpenAPI spec into Postman or Swagger UI to generate a full client, and follow the 10-minute tutorial for the end-to-end flow.