Event Capture

Send analytics events to Voidhash with a project secret key or a publishable token.

Event capture is how you send analytics events to Voidhash without a mobile SDK. Use it from your backend, or from custom clients that do not run one of the SDKs.

There are two endpoints. POST /i/v1/capture sends one event, and POST /i/v1/batch sends one or more events in a single request.

Choose a credential

Which credential you send depends on where the request comes from. A backend you control sends its secret key in the x-secret-key request header, as x-secret-key: vh_sk_.... A distributed client, such as an app or web bundle, cannot hold a secret, so it sends the publishable key as a token field in the JSON body, as "token": "vh_pk_...".

Send one or the other. Only the x-secret-key header may carry a secret key. A secret key in the body token field is rejected with 401 unauthorized, and a distributed client must not hold a secret key at all.

Voidhash SDKs may also present the publishable key through their usual x-publishable-key header instead of the body. When both the header and the body token are present, the body token wins.

Never expose a secret key

x-secret-key grants broad access to the project. Only send it from a server you control.

Capture one event

From a backend, authorize with the x-secret-key header and send the event in the body:

curl https://api.voidhash.com/i/v1/capture \
  -H "Content-Type: application/json" \
  -H "x-secret-key: $VOIDHASH_SECRET_KEY" \
  -d '{
    "sent_at": "2026-08-14T12:00:01.000Z",
    "uuid": "018f6d2e-4c3a-7b1d-9e5f-2a8c1b0d4e6f",
    "event": "paywall_viewed",
    "distinct_id": "user_123",
    "properties": { "location": "onboarding" },
    "context": { "platform": "ios", "app_version": "2.4.0" },
    "timestamp": "2026-08-14T12:00:00.000Z"
  }'

From a distributed client, drop the header and put the publishable token in the body instead:

curl https://api.voidhash.com/i/v1/capture \
  -H "Content-Type: application/json" \
  -d '{
    "token": "vh_pk_...",
    "sent_at": "2026-08-14T12:00:01.000Z",
    "uuid": "018f6d2e-4c3a-7b1d-9e5f-2a8c1b0d4e6f",
    "event": "paywall_viewed",
    "distinct_id": "user_123",
    "properties": {},
    "context": {}
  }'

Every event must include uuid, event, distinct_id, properties, and context. The request itself must include sent_at and exactly one credential.

The uuid is the deduplication key. Generate it once per event and reuse the same value if you retry, so a retried event is not counted twice.

Batch events

POST /i/v1/batch accepts the same event objects under an events array. The request has one request-level sent_at and the same credential choice as a single capture:

{
  "sent_at": "2026-08-14T12:00:05.000Z",
  "events": [
    {
      "uuid": "018f6d2e-4c3a-7b1d-9e5f-2a8c1b0d4e6f",
      "event": "paywall_viewed",
      "distinct_id": "user_123",
      "properties": {},
      "context": {}
    }
  ]
}

Clients using a publishable token add "token": "vh_pk_..." next to sent_at.

A successful request returns 202 with accepted and rejected counts. Log the x-request-id response header so you can reference it when diagnosing ingestion.

Retries

Retry network failures, 429, 500, and 503 responses with exponential backoff. Honor the retry-after header when it is present, and resend the same UUIDs so retries deduplicate.

Do not retry 400, 401, or 413 responses. Those indicate a problem with the request itself, so fix it and send again.

See the generated Event Capture endpoint pages for complete schemas and error bodies.