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.
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.
Analytics consent
Send x-analytics-consent: denied or x-analytics-consent: pending when the end user has not
granted usage analytics. Voidhash then stores none of the request's events: the response is still
202, with every event counted as rejected, so the client does not retry them. Omit the header,
or send granted, to record the events. The Voidhash SDKs send this header for you and stop
capturing while consent is withheld.
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.