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.