Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.onvy.health/llms.txt

Use this file to discover all available pages before exploring further.

ONVY webhooks let your integration react to changes without polling.

Configuration shape

Webhook delivery is configured per project:
{
  "url": "https://customer.example/webhooks/onvy",
  "events": ["users:created", "daily_records:updated", "ai_summaries:created"],
  "enabled": true,
  "hmac_secret": "replace-with-a-secret-of-at-least-16-characters"
}
Rules:
  • url must be HTTPS
  • events must contain only supported event names
  • enabled must be true to send deliveries
  • hmac_secret must be at least 16 characters

Delivery payload

One POST can contain multiple matching events. Up to 10 events are batched into a single delivery:
{
  "id": "wh_01J...",
  "created_at": "2026-03-05T18:10:27Z",
  "project_id": "proj_123",
  "org_id": "org_123",
  "api_version": 1,
  "events": [
    {
      "name": "daily_records:updated",
      "user_id": "user_123",
      "data": {
        "id": "score_123"
      }
    }
  ]
}
Routing metadata in the envelope (id, created_at, project_id, org_id, api_version, per-event name, optional user_id) is always plain JSON, even when individual event payloads are externalized.

Headers

  • X-Webhook-Signature
  • X-Webhook-Timestamp
  • X-Webhook-ID
The signature uses HMAC-SHA256 and the format sha256=<hex_digest>.

Verify signatures

import hashlib
import hmac


def verify_signature(raw_body: bytes, secret: str, header_value: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode("utf-8"),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, header_value)

Delivery semantics

  • Delivery is asynchronous and at-least-once: your endpoint must be idempotent.
  • Failed deliveries are retried with exponential backoff. The HTTP dispatch path retries on 429, 500, 502, 503, and 504 with total=3 and backoff_factor=1. Internal EventBridge dispatch into the webhook path is also retry-based and protected by a DLQ.
  • Action semantics are encoded in the event name (for example daily_records:created, daily_records:updated, daily_records:deleted).

Idempotency

Use these fields to deduplicate on your side:
  • The envelope id, also delivered as the X-Webhook-ID header, is unique per delivery.
  • Per-event payloads carry stable resource IDs (for example a daily_records event’s data.id matches the resource ID returned by the API).
Both fields are safe to use as idempotency keys.

Large payloads

Large event payloads can be externalized. When this happens, the event carries a url pointing to the full payload while routing metadata stays in the envelope. Your handler should fetch the URL when present rather than rely on inline data only.

Event families

The current public webhook catalog includes:
  • users:*
  • users.data_syncs:updated for provider sync state changes (for example deep-history loads)
  • facts:*
  • daily_records:*
  • ai_summaries:* for meal, sleep, workout, daily, weekly, nutrition, trend, and impact summaries
  • meals:*, including meals:updated when async nutrition analysis completes and a summary_id becomes available
  • custom_records:*
  • workouts:*
  • lab_tests:*
  • Batch lifecycle events such as batch.succeeded
Use the exact event names supported by your project configuration.
Many domain insights such as meal nutrition analysis or sleep summaries are delivered through ai_summaries:* rather than as standalone event families. See /ai-capabilities for the type catalog.