Webhook event reference
This page is the field contract for webhook payloads. For subscription management, signature verification, delivery semantics, and per-event JSON examples, see Webhooks.
Every payload has two layers:
- Core — the envelope plus
data.subject_ref,data.account_ref, and themoment,lifecycle,commerce, andattributionobjects. Stable within anapiVersion(currently2026-07). Code against these. - Enrichment — the
data.enrichmentobject, derived from the intent graph. Every field is optional and degrades to"unknown"or absent. Never let a receiver depend on one.
The routing rule
Enrichment and moment parameters are routing and segmentation inputs. They must never appear in copy a customer reads.
These fields exist so your automation can pick the right sequence, timing, and owner — not so your emails can narrate them back to the user.
- Good: the subject hit a seat cap, so your automation routes them into the seats sequence — whose copy you wrote for people who need more seats.
- Bad: pasting
wall_keyor a bucket name into a sentence a customer reads (“You hitseats.limitand your conversion likelihood is high, so…”).
The good version uses the field to choose the message. The bad version leaks internal machinery into the message, reads as surveillance, and breaks the moment it degrades to "unknown". If a value can be "unknown", it was never safe to put in a sentence.
Envelope fields (core)
| Field | Type | Nullable | Description |
|---|---|---|---|
apiVersion | string | no | Envelope version, currently "2026-07". Breaking changes ship under a new version. |
event | string | no | Event type — one of the 19 in the table below. |
webhookId | string | no | evt_<uuid>. Stable across retries; deduplicate on it. |
timestamp | string (ISO 8601) | no | When the moment occurred — not when the delivery was attempted. |
data — subject fields (core)
| Field | Type | Nullable | Description |
|---|---|---|---|
data.subject_ref | string | no | Pseudonymous reference to the end user (e.g. u_8f3a...), minted by your SDK integration. Resolvable only by you. |
data.account_ref | string | yes | Pseudonymous reference to the account/organization (acct_...), or null when there is no account context. |
data.moment (core)
Always present. Describes where in the flow the moment happened. Every field is nullable — events outside a wall context (for example trial.started) carry mostly nulls here.
| Field | Type | Nullable | Description |
|---|---|---|---|
wall_event_id | string | yes | Identifier of the underlying wall event, when the moment originated at a wall. |
wall_key | string | yes | Which wall — your configured key, e.g. seats.limit, exports.monthly. |
action | string | yes | What the orchestration did at the moment, e.g. upgrade_offer, guidance, fallback_offer, admin_request, sales_route. |
trigger_class | string | yes | The class of trigger, e.g. free_tier_limit, trial_gate, usage_wall, feature_gate. |
moment_type | enum | yes | pre_gate, at_gate, or post_value. |
reason_code | string | yes | Machine-readable reason for the moment, e.g. quota_exhausted, offer_declined. |
agent_environment | string | yes | The agent host where the moment happened, when known. |
data.lifecycle (core)
Always present. The subject’s subscription lifecycle at the time of the moment.
| Field | Type | Nullable | Description |
|---|---|---|---|
state | enum | no | free, trial, active, converted, or unknown. Degrades to "unknown" rather than blocking delivery. |
trial_ends_at | string (ISO 8601) | yes | Trial end date; null outside a trial. |
trial_days_remaining | number | yes | Whole days remaining; null outside a trial. |
usage_remaining_bucket | enum | no | none, low, some, or unknown. See bucket semantics. |
data.commerce (core — conversion.* events only)
Present only on conversion.trial_converted, conversion.upgrade_accepted, and conversion.entitlement_resumed. Absent — not null — on every other event. Individual fields may be null when not applicable to the transaction.
| Field | Type | Nullable | Description |
|---|---|---|---|
plan_key | string | yes | The plan involved in the conversion, e.g. growth_monthly. |
amount_cents | number | yes | Transaction amount in minor units. |
currency | string | yes | ISO 4217 currency code, lowercase, e.g. usd. |
branch | string | yes | Which conversion path produced it, e.g. acquisition (free/trial to paid) vs expansion (paid to bigger). |
rail | string | yes | How the conversion completed, e.g. in_thread. |
data.enrichment (every field optional)
Derived from the intent graph. Any field can be "unknown", null, or absent on any delivery — enrichment never delays or blocks delivery.
| Field | Type | Values | Description |
|---|---|---|---|
intent_state | enum | researching, deciding, purchasing, converted, churned, unknown | Where the subject sits in their journey. |
conversion_likelihood | enum | low, medium, high, unknown | Bucketed likelihood score. See bucket semantics. |
journey_momentum | enum | advancing, stalled, retreating, unknown | Direction of travel across recent interactions. |
journey_depth | number | 0–3 | How deep into the journey the subject is. |
is_returning | boolean | true / false | Whether the subject has been seen before. |
ai_provider | string | — | The AI provider behind the agent, when known. |
data.attribution (core)
| Field | Type | Nullable | Description |
|---|---|---|---|
params | string | no | Ready-made query string to append to campaign links, e.g. utm_source=inception_moment&utm_campaign=trial.expiring. |
Bucket semantics
Enrichment ships buckets rather than raw scores — buckets are stable to route on, and they make the degradation behavior explicit.
conversion_likelihood buckets an internal 0–100 score:
| Bucket | Score |
|---|---|
low | below 35 |
medium | 35 to below 70 |
high | 70 and above |
unknown | not enough signal to score |
usage_remaining_bucket describes headroom on the subject’s current entitlements:
| Bucket | Meaning |
|---|---|
none | at or over the limit |
low | close to the limit |
some | comfortable headroom |
unknown | usage signal unavailable |
Degradation. Every enrichment field degrades to "unknown" (or is absent) rather than blocking or delaying delivery. A delivery never waits on the intent graph. Treat "unknown" as its own routing branch — usually “take the default path” — not as an error.
Per-event reference
All 19 events carry the full core envelope. The table below shows what varies: whether the moment object is meaningfully populated, whether commerce is present, and the fields most receivers route on.
| Event | moment populated | commerce | Fields to watch |
|---|---|---|---|
moment.wall_hit | yes | absent | wall_key, trigger_class, moment_type, conversion_likelihood |
moment.upgrade_offered | yes | absent | wall_key, action |
moment.guidance_offered | yes | absent | wall_key, moment_type (typically pre_gate) |
moment.free_tier_reached | yes | absent | wall_key, usage_remaining_bucket (typically none) |
moment.post_value_offered | yes | absent | moment_type (post_value), journey_momentum |
moment.upgrade_abandoned | yes | absent | wall_key, reason_code, journey_momentum |
moment.fallback_accepted | yes | absent | wall_key, action |
moment.admin_upgrade_requested | yes | absent | account_ref, wall_key |
moment.sales_route_captured | yes | absent | account_ref, conversion_likelihood |
moment.blocker_reported | yes | absent | wall_key, reason_code |
trial.started | mostly null | absent | lifecycle.trial_ends_at, trial_days_remaining |
trial.activated | mostly null | absent | lifecycle.trial_days_remaining, journey_depth |
trial.expiring | mostly null | absent | trial_days_remaining, usage_remaining_bucket, intent_state |
trial.expired | mostly null | absent | lifecycle.state, trial_ends_at |
conversion.trial_converted | mostly null | present | commerce.plan_key, branch, amount_cents |
conversion.upgrade_accepted | mostly null | present | commerce.plan_key, branch, account_ref |
conversion.entitlement_resumed | mostly null | present | commerce.plan_key |
value.delivered | partial | absent | moment_type (post_value), journey_momentum |
subject.activation_reached | mostly null | absent | lifecycle.state, journey_depth |
“Mostly null” means the moment object is present (core is stable) but its nullable fields typically carry nulls, because the event did not originate at a wall. Write receivers accordingly: check for null on every moment field, on every event.
Per-event JSON examples with realistic values are in the event catalog.
PII posture
Webhook payloads contain no emails and no names — ever. There is no field for them in any event, and none will be added within this apiVersion.
The subject_ref contract:
- Refs are minted by your SDK integration (your
getSubjectRef), not by Inception. Inception stores and forwards the ref as an opaque string. - Refs are stable per user, so they work as join keys in your warehouse and as custom properties in your email platform or CRM.
- Only you can resolve a ref back to a person. A leaked payload identifies no one to anyone who is not already holding your user database.
account_ref follows the same contract at the account/organization level.
Practical implication: any workflow that ends in an email being sent must pass through something you control — a receiver or a pre-synced ref-to-contact mapping — to translate the ref. The standard patterns are in Trigger email campaigns from Revenue Moments.