> ## Documentation Index
> Fetch the complete documentation index at: https://docs.puntego.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Analytics events

> The outcome events Puntego sends to Amplitude, Mixpanel, PostHog, Segment and your webhook: catalog puntego.v1.

Every analytics integration and the `outcome_event` webhook topic send the same catalog,
`puntego.v1`: one event per thing that happened in the guide. Each event carries only properties
Puntego defines and types: outcomes, counts, ratings and scores, your guide, revision, experiment and
declared target IDs, a page route with IDs collapsed and no query string, language and device class.

Puntego never sends what anyone typed or read: no questions, answers, notes, survey text, form
values, selectors, page text, accessible names or URLs with query strings. Every value is re-read
through the catalog's strict shapes just before it is sent; a value outside its shape is dropped,
never copied.

## The envelope

Each event has the same envelope:

| Field | Meaning |
| - | - |
| `event_id` | Stable ID of the fact (`cr:<conversation>`, `ga:<action>`…). Re-sending the same fact reuses it. |
| `name` | The catalog name below, for example `conversation_resolved`. |
| `occurred_at` | When it happened (ISO 8601, UTC). |
| `schema` | The catalog version, `1`. |
| `context` | `environment`\* (`production` · `development` · `staging`), `identified`\* (signed-in customer), `route_key` (the page, IDs collapsed, no query string), `locale`, `device_class` (`desktop` · `mobile` · `tablet`), `surface` (`widget` · `headless` · `sdk` · `voice`), `conversation_id`, `experiment_id`, `variant_id`. |
| `properties` | The event's own properties (below). |

`*` marks a value that is always present. Route keys collapse segments that look like IDs
(`/invoices/8812/edit` becomes `/invoices/*/edit`) and keep a hash route (`/#/billing`).

## Catalog

Choose the groups each integration sends in its panel under **Dashboard → Settings →
Integrations**. `tour_step_viewed` is high-volume and stays off until you turn on **Tour steps**.

### Conversations (`conversations`)

| Event | Sent to tools as | When | Properties |
| - | - | - | - |
| `conversation_started` | Puntego Conversation Started | The visitor sends the first message of a conversation. | `entry`\* (`assist_api` · `launcher` · `proactive` · `struggle_chip` · `suggestion` · `unknown` · `voice`) |
| `conversation_resolved` | Puntego Conversation Resolved | The conversation is resolved for the first time: the visitor confirmed it, or it went quiet after an answer (assumed). | `guided` (boolean), `resolution`\* (`assumed` · `confirmed`), `user_turns` (integer 0–100000) |
| `conversation_resolution_verified` | Puntego Resolution Verified | Puntego's resolution check labels a resolved conversation. | `label`\* (`uncertain` · `unverified` · `verified`) |
| `conversation_escalated` | Puntego Conversation Escalated | The conversation is handed to a person. | `channel` (code) |
| `conversation_abandoned` | Puntego Conversation Abandoned | The visitor left without an answer that helped, or said it did not help. | `reason`\* (`idle` · `said_not_helpful`) |
| `conversation_outcome_changed` | Puntego Conversation Outcome Changed | A conversation that already had an outcome gets a different one. | `from`\* (`abandoned` · `escalated` · `resolved`), `to`\* (`abandoned` · `escalated` · `resolved`) |

### Answers (`answers`)

| Event | Sent to tools as | When | Properties |
| - | - | - | - |
| `question_unanswered` | Puntego Question Unanswered | A question landed in the Unanswered tab of the Inbox: no answer, no source, a stale source, or an answer too weakly grounded. | `question_group_id` (null), `reason`\* (`no_answer` · `no_source` · `stale_source` · `weak_grounding`) |
| `answer_rated` | Puntego Answer Rated | An answer is rated, or its rating changes. | `has_note` (boolean), `rating`\* (`helpful` · `unhelpful`), `source` (`api` · `dashboard` · `visitor`) |

### Actions (`actions`)

| Event | Sent to tools as | When | Properties |
| - | - | - | - |
| `action_proposed` | Puntego Action Proposed | The guide proposes an action on the page. | `action_type`\* (code), `confirmed` (boolean), `duration_ms` (integer 0–86400000), `failure_code` (code), `risk`\* (`commit` · `input` · `navigation` · `visual`), `target_id` (id) |
| `action_completed` | Puntego Action Completed | An action finished and its result was observed. | `action_type`\* (code), `confirmed` (boolean), `duration_ms` (integer 0–86400000), `failure_code` (code), `risk`\* (`commit` · `input` · `navigation` · `visual`), `target_id` (id) |
| `action_failed` | Puntego Action Failed | An action could not be completed. | `action_type`\* (code), `confirmed` (boolean), `duration_ms` (integer 0–86400000), `failure_code` (code), `risk`\* (`commit` · `input` · `navigation` · `visual`), `target_id` (id) |
| `action_declined` | Puntego Action Declined | The visitor declined a proposed action. | `action_type`\* (code), `confirmed` (boolean), `duration_ms` (integer 0–86400000), `failure_code` (code), `risk`\* (`commit` · `input` · `navigation` · `visual`), `target_id` (id) |

### Guides and tours (`guides`)

| Event | Sent to tools as | When | Properties |
| - | - | - | - |
| `tour_started` | Puntego Tour Started | The first step of a tour is shown. | `experience_id` (id), `revision_id` (id), `source`\* (`assistant` · `experience` · `sdk`), `step_count` (integer 1–50), `step_index` (integer 1–50) |
| `tour_step_viewed` (off by default) | Puntego Tour Step Viewed | A tour step is shown. Off unless you turn it on. | `experience_id` (id), `revision_id` (id), `source`\* (`assistant` · `experience` · `sdk`), `step_count` (integer 1–50), `step_index` (integer 1–50) |
| `tour_completed` | Puntego Tour Completed | The last step of a tour is shown. | `experience_id` (id), `revision_id` (id), `source`\* (`assistant` · `experience` · `sdk`), `step_count` (integer 1–50), `step_index` (integer 1–50) |
| `experience_viewed` | Puntego Guide Viewed | A guide you published is shown. | `deployment_id` (id), `experience_id`\* (id), `format`\* (`announcement` · `checklist` · `form` · `nudge` · `suggestion` · `survey` · `tour`), `revision_id`\* (id) |
| `experience_completed` | Puntego Guide Completed | The visitor completes a guide. | `deployment_id` (id), `experience_id`\* (id), `format`\* (`announcement` · `checklist` · `form` · `nudge` · `suggestion` · `survey` · `tour`), `revision_id`\* (id) |
| `experience_dismissed` | Puntego Guide Dismissed | The visitor dismisses a guide. | `deployment_id` (id), `experience_id`\* (id), `format`\* (`announcement` · `checklist` · `form` · `nudge` · `suggestion` · `survey` · `tour`), `revision_id`\* (id) |
| `survey_answered` | Puntego Survey Answered | A survey is answered. The score or the chosen option only, never written answers. | `experience_id`\* (id), `option_id` (id), `question_kind`\* (`choice` · `nps` · `scale`), `revision_id`\* (id), `score` (number 0–100) |
| `suggestion_clicked` | Puntego Suggestion Clicked | The visitor clicks a suggested question. | `suggestion_id` (id) |

### Struggle and help offers (`struggle`)

| Event | Sent to tools as | When | Properties |
| - | - | - | - |
| `struggle_detected` | Puntego Struggle Detected | A page view showed signs the visitor was stuck (counts per signal). | `asked_for_help` (boolean), `signals`\* (counts per signal), `top_target_id` (id) |
| `help_offer_shown` | Puntego Help Offer Shown | Puntego offers help to a visitor who looks stuck. | `offer_id`\* (id), `topic`\* (`error` · `form` · `page`) |
| `help_offer_accepted` | Puntego Help Offer Accepted | The visitor accepts the offer of help. | `offer_id`\* (id), `topic`\* (`error` · `form` · `page`) |

Struggle signals are `back_and_forth`, `dead_click`, `failed_submit`, `form_error_idle`, `host_error`
and `rage_click`, each counted 1–20 per page view. `top_target_id` names the control you declared in
the manifest that collected most signals, and only once that control has at least five page views on
the page in the last 30 days.

## Versioning

Adding an event or an optional property keeps `schema: 1`. Renaming, removing or retyping anything
becomes `schema: 2`, and both versions are sent side by side for 90 days under different event IDs.

## How each tool receives it

| | Amplitude | Mixpanel | PostHog | Segment |
| - | - | - | - | - |
| Event name | `event_type` | `event` | `event` | `event` (Track) |
| Deduplication | `insert_id` | `$insert_id` | `uuid` | `messageId` |
| Time | `time` (ms) | `time` (s) | `timestamp` | `timestamp` |
| Pseudonymous identity | `device_id` | `distinct_id` | `distinct_id`, no person profile | `anonymousId` |
| Customer external ID | `user_id` | `distinct_id` | `distinct_id` | `userId` |
| Account (external ID mode) | `groups.account` | `account_id` | `$groups.account` | `context.groupId` |

Tools take flat properties, so the envelope arrives prefixed: `puntego_schema`, `puntego_event`,
`puntego_event_id`, then every context and event property as `puntego_<key>` (for example
`puntego_route_key`, `puntego_resolution`). Struggle counts become one number per signal
(`puntego_signal_rage_click`). Mixpanel always receives `ip: 0`, so the Worker's address is never
geolocated onto your visitor.

Deliveries are sent in batches of up to 100 events, every minute. The deduplication ID is stable
across retries, so a retried batch never double-counts.

Data you send to your own tools is governed by your agreement with that provider. Deleting a
customer in Puntego stops future deliveries but does not delete what your tool already received.

## Webhooks

The `outcome_event` topic sends the same catalog to your webhook endpoint, one request per event:

```json theme={null}
{
  "event": "outcome_event.conversation_resolved",
  "id": "whd-outcome-…",
  "data": {
    "event_id": "cr:conv_8f2c",
    "name": "conversation_resolved",
    "occurred_at": "2026-10-20T14:03:11.000Z",
    "schema": 1,
    "context": { "environment": "production", "identified": true, "route_key": "/billing" },
    "properties": { "guided": true, "resolution": "confirmed", "user_turns": 3 },
    "customer_id": "…",
    "visitor_id": "…"
  },
  "timestamp": 1792504991
}
```

Every request carries these headers:

| Header | Value |
| - | - |
| `x-puntego-event` | The event, for example `outcome_event.conversation_resolved`. |
| `x-puntego-timestamp` | Seconds since the epoch when the request was signed. |
| `x-puntego-signature` | `t=<timestamp>,v1=<hex>`: HMAC-SHA256 of `<timestamp>.<raw body>` with your endpoint's signing secret. |
| `x-puntego-schema` | `1`, on `outcome_event` requests only. |

To verify a request, compute the HMAC-SHA256 of the timestamp, a period and the raw request body
(before any JSON parsing) with your signing secret, compare it with `v1` in constant time, and reject
timestamps more than five minutes old. Use `data.event_id` to discard a repeated delivery: each delivery
is attempted up to six times.

No webhook topic carries what anyone typed or read: `runtime_event` sends the event's name, IDs and
time without its properties, `guide_action_log` leaves out the model's reason, and
`experience_engagement` sends survey scores and chosen options but never written answers. Every
webhook about a person is sent only under that person's current consent. Puntego's own telemetry
never leaves: these internal events are recorded for you in Puntego and in the export API, and are
not sent to webhooks or analytics tools: `ai_disclosure_seen`, `conversation.turn_completed`,
`install_verified`, `llm_usage`, `llm_usage_group_completed`, `magic_moment.latency`,
`pii/token-miss`, `voice.failover`, and every `customer.*` and `workspace.*` event.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.