Skip to content
anecticoDocsDashboard
Browse documentation
Guide

Capture product events

Measure application milestones, declare sampling, and inspect capture quality.

On this page

Product events record meaningful application milestones such as checkout_submitted, payment_failed, or agent_step_completed.

Use them to measure workflows and connect customer behavior with errors, traces and replay. Capture is explicit; broad automatic clickstream collection is not enabled.

Use a project-scoped key with analytics:write. Add ingest:write only when the same SDK also sends traces, logs, metrics, or errors.

Capture an event

JavaScript / TypeScript

import { AnalyticsClient } from '@anectico/sdk/analytics';

const events = new AnalyticsClient({
  endpoint: 'https://api.anectico.com',
  apiKey: process.env.ANECTICO_API_KEY!,
});

events.capture('checkout_submitted', {
  cart_size: 3,
  payment_provider: 'stripe',
});

Python

import os
import anectico

events = anectico.AnalyticsClient(
    endpoint='https://api.anectico.com',
    api_key=os.environ['ANECTICO_API_KEY'],
)
events.capture('checkout_submitted', {
    'cart_size': 3,
    'payment_provider': 'stripe',
}, distinct_id='user_8842')

Go

events, err := anectico.NewAnalytics(anectico.AnalyticsConfig{
	Endpoint: "https://api.anectico.com",
	APIKey:   os.Getenv("ANECTICO_API_KEY"),
})
if err != nil {
	return err
}
defer func() { _ = events.Stop(ctx) }()

err = events.Capture(ctx, "checkout_submitted", map[string]any{
	"cart_size":        3,
	"payment_provider": "stripe",
}, anectico.WithDistinctID("user_8842"))

Swift (iOS)

Anectico.capture("checkout_submitted", properties: [
    "cart_size": 3,
    "payment_provider": "stripe",
])

Kotlin (Android)

Anectico.capture(
    "checkout_submitted",
    mapOf("cart_size" to 3, "payment_provider" to "stripe"),
)

React Native

await Anectico.capture('checkout_submitted', {
  cart_size: 3,
  payment_provider: 'stripe',
});

Flutter

await Anectico.capture('checkout_submitted', properties: {
  'cart_size': 3,
  'payment_provider': 'stripe',
});

Stop the event client during graceful shutdown so buffered events can finish sending. In JavaScript, the event client shares the active identity and browser session with the main SDK. Python and Go can use the shared identity or an explicit request-scoped ID as shown above. Go's batch-oriented Analytics.Identify queues a person merge without changing the process-global identity, so one server can synchronize multiple people without attributing later background telemetry to the last person. Use client.Identify only for the stateful single-user/client flow. Do not create a second customer ID field inside event properties.

If you call POST /api/v1/capture directly, send exactly one JSON envelope containing the events array. Concatenated JSON documents are malformed; the entire request is rejected and no prefix of the batch is accepted.

When to retry

If you call POST /api/v1/capture directly, retry on 429 and on any 5xx, with backoff. A 503 carries Retry-After and means the request never landed — this is also the answer when the upload is cut short in transit, so a truncated or reset connection is safe to send again.

Follow the validated acknowledgement's retry instruction for each original position. An explicit invalid-input refusal requires a correction; an oversized request must be split. An unrecognized response remains uncertain at any HTTP status. Preserve original IDs, timestamps and payloads when retrying uncertain positions, and do not resend accepted siblings. See capture acknowledgements.

Know when events were dropped

capture() is non-blocking: it queues the event and returns before anything is sent, so it can never tell you whether delivery worked. Three surfaces do.

flush() tells you whether everything queued was accepted. It reports success only when every attempted event received a validated queue acknowledgement, or the queue was empty. HTTP 2xx alone is insufficient, and queue acceptance does not prove query visibility. When explicit refusals were dropped it reports failure by value — false in JavaScript and Python, an error wrapping ErrAnalyticsDropped in Go. JavaScript and Python raise when uncertain events remain queued; Go reports ErrAnalyticsRetryPending. A mixed outcome can contain both drops and uncertain events; inspect the counters and callbacks as well. Go errors can match both ErrAnalyticsDropped and ErrAnalyticsRetryPending.

A delivery handler tells you the moment a batch is dropped. Pass onDeliveryError / on_delivery_error / OnDeliveryError when you construct the client and it is called once per dropped group of positions with the response status, the number dropped, and a stable lower-case reason code such as unauthorized or payload_too_large. Retryable failures never call it — those events are still queued. Per-item refusals can arrive in an HTTP 200 ledger; a drop count is not proof that the corresponding user action never reached storage through an earlier attempt.

A counter tells you the running total. stats() / Stats() reports recorded, delivered (events the server accepted), dropped, and queued. A non-zero dropped is worth alerting on: the most common cause is a revoked or wrong-project API key, which rejects every batch with 401 or 403 while the rest of your application carries on unaware.

Each drop is also written to the SDK's log at WARN, naming the status and the count.

In React Native, delivery happens inside the native queue, so flush() there reports that the flush attempt finished and nothing more; see the React Native reference for what its bridge does report.

Choose useful events

Record events that help an engineer answer a production question:

  • a workflow entered a meaningful state;
  • a rollout or provider choice changed behavior;
  • a customer-visible operation succeeded or failed; or
  • an agent or background job completed a significant step.

Avoid high-volume noise such as every mouse movement, DOM mutation, or internal function call.

Verify

Open Analytics → Events & trends, select the event, and pivot to a customer who triggered it. The event should also appear in that customer's timeline alongside surrounding errors, traces, and replay.

Declare product-event sampling

If your application or collector samples product events before capture, attach its declaration to each submitted event's properties. Existing SDK property maps accept this object:

{
  "environment": "production",
  "$anectico_product_sampling": {
    "source": "checkout-producer",
    "rate": 0.25
  }
}

The reserved object must contain exactly source and rate. source is a non-sensitive identifier of 1–64 ASCII characters: start with a letter or digit, then use letters, digits, periods, underscores, colons or hyphens. Use a producer label, never a person identifier or credential. rate must be a finite JSON number greater than zero and at most one; strings, null, extra fields and out-of-range values are refused as INVALID_EVENT. This property applies only to product events; identify and $groupidentify cannot carry it. Other valid items in the same batch still follow their own acknowledgements.

A rate below one is declared_sampled; one is declared_unsampled. An absent object is unknown. The declaration is retained with the stored event and its frozen source evidence. It reports what the client says; it does not identify an authorized source, prove that all upstream actions were captured, or change collection behavior. Keep it unchanged when retrying an uncertain event. Analytics measures observed events and does not scale funnel or retention results by this rate. Overall source completeness remains unknown even when every submitted event declares rate one.

This product-event declaration is separate from temporary diagnostic trace sampling grants. It does not measure events withheld by consent, missing instrumentation or upstream drops.

Inspect capture-attempt quality

After sending events, inspect the project's retained attempt evidence with the CLI:

anectico --project <project-id> analytics quality \
  --start-time 2026-09-01T00:00:00Z --end-time 2026-09-02T00:00:00Z \
  --environment production

For MCP, discover the get_capture_quality read action, then call:

{"name":"execute_read_action","arguments":{"action":"get_capture_quality","arguments":{"project_id":"<project-id>","start_time":"2026-09-01T00:00:00Z","end_time":"2026-09-02T00:00:00Z","environment":"production"}}}

Both require analytics:read; MCP additionally requires mcp:read. Capture-only analytics:write cannot inspect attempts. Product and identity-control counts retain their pending, queued, uncertain and not-attempted states; invalid and quota-refused positions are reported separately. Counts include submitted retries and use coarse UTC-hour overlap. Invalid positions cover all retained project attempts because their time/environment cannot be trusted. Queued is not consumed, and zero counts do not establish completeness. Check SDK delivery callbacks and query actual events to investigate uncertainty; do not automatically resend a whole batch based on these counts. See the response contract for limits, errors and the meaning of the digest.

The same response includes sampling_provenance: "declared", sampling_declarations grouped by source and rate, and sampling_unknown for product positions without a declaration. Each group separates publication states from quota refusals. These counts include retries; two submissions with the same message ID can appear in different declaration groups while storage retains the original event. Identity-control and invalid-input counts remain separate. At most 100 distinct source/rate groups can appear in the selected window; exceeding the limit refuses the entire quality read. source_completeness stays unknown, including for empty results.