Skip to content
Console
Browse documentation
Reference

Integration event verification

Prove that the exact product events your application test sent arrived, under the right identity and against your tracking plan.

On this page

After your agent wires product events into an application, it runs a controlled test and then asks Anectico one question: did these exact events arrive, under this identity, and do they match this tracking plan revision? The verification read answers that question for up to 20 samples at a time. It never sends events, changes nothing, stores no report and runs no model; it reports what the readable evidence shows, check by check.

The same request works on every surface:

Surface How
REST POST /api/v1/instrumentation/events/verify with the JSON request as the body
MCP execute_read_action with action verify_integration_events and the JSON request as arguments
CLI anectico doctor events --file request.json (plus the usual --project and --json)

Project-bound keys may omit project_id; naming a different project is refused. The CLI refuses a file whose project_id contradicts --project.

The request

{
  "project_id": "2d3c9f8e-1a1b-4c5d-9e0f-123456789abc",
  "window_start": "2026-10-15T09:00:00Z",
  "window_end": "2026-10-15T10:00:00Z",
  "samples": [
    {
      "event": "checkout_completed",
      "message_id": "test-run-42-checkout",
      "source_id": "7f1e2d3c-4b5a-4968-8776-5544332211aa",
      "distinct_id": "test-person-42",
      "session_id": "test-session-42",
      "groups": {"company": "acme"}
    }
  ],
  "tracking_plan_id": "c0ffee00-1234-4abc-8def-0123456789ab",
  "tracking_plan_revision": "3"
}
Field Meaning and bounds
window_start, window_end Event-time window [start, end), millisecond precision, at most one hour.
samples 1 to 20 samples; the answer keeps their order. Each (event, message_id, source_id) appears once.
samples[].event Exact event name, at most 200 bytes.
samples[].message_id The exact message_id your application sent with the event. Send your own stable ID in tests.
samples[].source_id The ID of the source that admitted the event: the sending key's source_id, or the project's default source when the key names none (see native intake keys). It is taken from the credential, never from the event.
samples[].distinct_id The identity the application declared. It is checked as an alias, never treated as a person ID.
samples[].session_id Optional. Present means "check it"; an empty string checks for an empty session. Omit it to skip the check.
samples[].groups Optional. The account the event must carry: group type to group key, as the application sent them in properties.$groups or as the SDK attached them after its group call. Up to 20 entries, each a printable string of at most 200 bytes. Every named entry is compared; accounts the event carries that you do not name are ignored. Omit it to skip the account check.
samples[].property_checks Optional, only without a tracking plan: up to 20 { "key", "required", "types" } checks. required must be written as true or false; types is a set of null, boolean, number, string, array, object. The key $groups is refused: Anectico keeps the account an event carries as its account, not as a property, so check it with groups.
tracking_plan_id, tracking_plan_revision Optional pair: the plan and an exact positive revision written as a decimal string. Neither half alone is valid. The latest revision is never substituted.

Identify and group-identify calls store no event row, so they cannot be selected; verify the events that follow them instead.

The answer

Every check has a state and, unless it matched, a reason:

State Meaning
observed_match The readable evidence matches the request.
mismatch The evidence was read and differs (for example different_distinct_id, type_mismatch, required_missing, event_not_declared).
not_observed A complete, readable read of the whole window found no such event (or Anectico holds no exact alias for the declared identity).
withheld Your key lacks the scope or the project's content policy withholds it. The reason names which. Never a sign of absence.
unavailable A limit, a failure or the edge of readable history prevented the check. The reason names which. Never a sign of absence.
ambiguous More than one stored event matches the selection; none is chosen for you.

Each result also has an outcome: verified (every check observed_match), failed (a mismatch or a not_observed event) or undetermined (anything else). population counts requested samples: requested, observed, not_observed, ambiguous, undetermined, verified and failed.

Per sample you get:

  • event, plus observed — the stored event's name, message ID, source, origin (browser_product, server_product, mobile_product, otlp or platform_product), distinct ID, session, event time and trusted receipt time — only when exactly one event matched.
  • distinct_id and, when requested, session_id checks against that event.
  • groups, when requested: observed_match when the stored event carries every named group type with exactly that key, otherwise mismatch with group_missing or different_group_key. The stored accounts themselves are never returned. A plan that declares $groups as a property gets not_supported (account_memberships_use_groups) for that key, never absent.
  • identity — with persons:read, the canonical person_id the declared alias resolves to.
  • plan_declaration — with a plan, whether that exact revision declares the event. An unrelated or empty plan never matches; a plan that declares the event with no properties does.
  • properties — one entry per declared or requested key: required, expected_types, expectation_source (tracking_plan or request), state, reason, observed_presence (present/absent) and the literal observed_type. Values, undeclared key names and hidden-key counts are never returned. Extra undeclared properties are not a mismatch.

The response also carries checked_at, the read_cutoff (events received after it are not read; it is not a snapshot or delivery watermark), the read's start and finish clocks, the plan that was loaded with its definition_hash, limits, plain limitations, read_coverage (the window actually read and the readable-history floor) and an always-empty links: there is no proof page for this read.

Scopes

Scope What it adds
analytics:read Required. Event, distinct ID, session and account (groups) checks. (MCP also needs mcp:read.)
tracking_plans:read Inspecting the pinned plan. Without it the plan and declarations are withheld.
persons:read Canonical identity. Without it identity is withheld, and a stored identity that differs from the declared one is reported only as a mismatch.
agents:content:read Property checks. Without it every property check is withheld. Sensitive declarations, content policy and content history can still withhold a property.

A withheld check never lets a sample verify. If a scope or the project's policy changes while the read runs, or the selected events change (for example a person is erased), no report is released: the call fails and you start a fresh read.

Limits

The limits are fixed by Anectico and returned in every answer under limits:

Limit Value
Window 1 hour
Samples 20
Property checks per sample 20
Rows scanned 10,000,000
Bytes scanned 1 GiB
Memory 512 MiB
Candidate events returned 200
Returned bytes 4 MiB
Execution time 8 seconds
Concurrent verifications per project 2

The read examines every retained event of the selected names in the project, not only the window, so a project with several million retained events of those names can reach the byte or row limit; choose fewer distinct event names per call in that case.

When a read reaches a limit, every sample is unavailable with a limit_… reason and limits.limit_reached is true; nothing is reported as not_observed. A third concurrent verification for the same project is refused (HTTP 429); retry shortly. Normal rate limits and agent budgets apply; there is no separate fee.

Using it in a test

  1. Generate a fresh message_id for each test event and keep the distinct_id and session your test uses.
  2. Run the test, then wait for the capture acknowledgement. Acknowledgement means the event was accepted, not that it can be read yet.
  3. Verify. If an event is not_observed right after the test, wait briefly and verify again with the same request. Do not resend the event to make it appear.
  4. Report exactly what was verified. A verified sample says nothing about other events, other people or whether everything your application emitted arrived.

To verify feature-flag exposures against their flag definition version, see feature-flag exposure verification. To verify either job through one request with a single verdict, see integration job verification.

Two sources that send the same event name, timestamp, distinct ID and message ID keep one stored event, so verifying one source can report not_observed because the other source's copy was kept. Use a unique message_id per source in tests.

For the stages between your code and stored evidence, what a verified sample does not prove and what has been run, see What integration verification proves and what it does not.

See also verify your setup, tracking plans and event property schemas, which describe whole-project observation rather than one exact event.

Experiment receipt handoff

Read one launched subject through get_experiment_delivery_evidence in execute_read_action, or anectico experiments delivery-evidence KEY --revision REV --assignment-id ID --file outcomes.json. The equivalent REST read is POST /api/v1/projects/{projectId}/experiments/{key}/delivery-evidence. It requires experiments:read; canonical attribution and the alternative distinct_id selector require persons:read. This read returns existing receipts and the frozen outcome declaration; it does not run the independent capture checks on this page.

Preserve the application's actual event, declared identity, source, message ID and optional session and run selectors. Receipt (source_id,event_id) must match sample (source_id,message_id); the observed event must match the frozen launched outcome and the sample's resolved person must match readable assignment attribution. Without that sample or attribution, do not report an outcome as verified. A provisional receipt is not stored-capture proof; reconciled is an existing settlement rather than a fresh read. No selected outcomes means assignment and exposure evidence only. See subject delivery evidence for states, limits and the isolated smoke-test boundary. The composed experiment verification job is not available; use the two explicit reads.

Survey receipt handoff

Read get_survey_delivery_evidence through execute_read_action, or anectico surveys delivery-evidence SURVEY_ID --revision REV --person TEST_ID --start RFC3339 --end RFC3339. REST uses a semantic POST read at /api/v1/projects/{projectId}/surveys/{surveyId}/delivery-evidence. The exact launched revision is a decimal string, and the receipt-time window is at most one hour. Optional shown_id and outcome_id preserve presence and compare the caller's IDs only under content authority.

Retained offers, explicit displays, first contact and outcomes are distinct facts; independent stored events are another check. Only an established emission binding can supply a message ID or response group. A historical unbound stage cannot be reconstructed. An incomplete subject lookup, unreadable history, failed source or budget limit is undetermined, never proof that no response arrived. No answer values or proof page are returned. Current and eligible earlier identities are checked together; identity correction, erased or unknown history and population limits can refuse that check. An unavailable lookup is distinct from a genuinely unresolved visitor. This is a selected diagnostic, with current permissions and erasure checks; it neither delivers a survey nor proves every answer arrived. See visitor delivery evidence for the selection, conditional scopes, limits and isolated qualification boundary.