Skip to main content

Webhooks Events

We currently support the following event types:

Event nameDescription
feature.createdTriggered when a feature is created
feature.updatedTriggered when a feature is updated
feature.deletedTriggered when a feature is deleted
feature.saferollout.shipTriggered when a safe rollout is completed and safe to rollout to 100%.
feature.saferollout.rollbackTriggered when a safe rollout has a failing guardrail and should be reverted.
feature.saferollout.unhealthyTriggered when a safe rollout is failing a health check and may not be working as expected.
feature.rampSchedule.createdTriggered when a ramp schedule is created for a feature
feature.rampSchedule.deletedTriggered when a ramp schedule is deleted from a feature
feature.rampSchedule.actions.startedTriggered when a feature ramp schedule starts
feature.rampSchedule.actions.completedTriggered when a feature ramp schedule completes all steps
feature.rampSchedule.actions.rolledBackTriggered when a feature ramp schedule is rolled back or reset to start
feature.rampSchedule.actions.jumpedTriggered when a feature ramp schedule is jumped to a specific step
feature.rampSchedule.actions.step.advancedTriggered when a feature ramp schedule advances. Overdue steps are caught up in a single advance: when currentStepIndex - previousStepIndex > 1, the intermediate steps were folded into this one event (one revision publish) rather than fired individually.
feature.rampSchedule.actions.step.approvalRequiredTriggered when a feature ramp step is waiting for approval
feature.rampSchedule.actions.awaitingStartApprovalTriggered when a feature ramp schedule is published but held at the start, awaiting an explicit start approval
feature.rampSchedule.actions.startApprovedTriggered when a held ramp schedule's start is approved by a user
feature.revision.createdTriggered when a new draft revision is created for a feature
feature.revision.updatedTriggered when a draft revision is modified (rules, default value, toggles, prerequisites, metadata, etc.). The change field indicates the specific kind of mutation.
feature.revision.reviewRequestedTriggered when a draft revision is submitted for review
feature.revision.approvedTriggered when a draft revision is approved by a reviewer
feature.revision.changesRequestedTriggered when a reviewer requests changes on a draft revision
feature.revision.commentedTriggered when a comment is added to a draft revision
feature.revision.discardedTriggered when a draft revision is discarded
feature.revision.reopenedTriggered when a discarded draft revision is reopened as a draft
feature.revision.rebasedTriggered when a draft revision is rebased onto the latest published version
feature.revision.publishedTriggered when a draft revision is published. Overlaps with feature.updated but provides revision-specific context (base version, comment, author).
feature.revision.revertedTriggered when a feature is reverted to a previous published revision
feature.revision.publishFailedTriggered when a deferred publish (scheduled publish or auto-publish-on-approval) is given up on after failing — terminally, or after exhausting retries. The draft is left open for a human to resolve.
experiment.createdTriggered when an experiment is created
experiment.updatedTriggered when an experiment is updated
experiment.deletedTriggered when an experiment is deleted
experiment.warningTriggered when a warning condition is detected on an experiment
experiment.info.significanceTriggered when a goal or guardrail metric reaches significance in an experiment (e.g. either above 95% or below 5% chance to win). Be careful using this without Sequential Testing as it can lead to peeking problems.
experiment.info.scheduled-status-updateTriggered when a scheduled start or stop is automatically applied to an experiment, including the auto-ship outcome for a scheduled end.
experiment.decision.shipTriggered when an experiment is ready to ship a variation.
experiment.decision.rollbackTriggered when an experiment should be rolled back to the control.
experiment.decision.reviewTriggered when an experiment has reached the desired power point, but the results may be ambiguous.
savedGroup.createdTriggered when a saved group is created
savedGroup.updatedTriggered when a saved group is updated
savedGroup.deletedTriggered when a saved group is deleted
savedGroup.revision.createdTriggered when a new draft revision is created for a saved group
savedGroup.revision.updatedTriggered when a draft revision's proposed changes are modified (values, condition, archive, or metadata). The change field indicates the kind of mutation.
savedGroup.revision.reviewRequestedTriggered when a draft revision is submitted for review
savedGroup.revision.approvedTriggered when a draft revision is approved by a reviewer
savedGroup.revision.changesRequestedTriggered when a reviewer requests changes on a draft revision
savedGroup.revision.commentedTriggered when a comment is added to a draft revision
savedGroup.revision.discardedTriggered when a draft revision is discarded
savedGroup.revision.rebasedTriggered when a draft revision is rebased onto the latest live state
savedGroup.revision.publishedTriggered when a draft revision is published. Overlaps with savedGroup.updated but provides revision-specific context.
savedGroup.revision.revertedTriggered when a saved group is reverted to a previous published revision
savedGroup.revision.reopenedTriggered when a discarded revision is reopened
savedGroup.revision.publishFailedTriggered when a deferred publish (scheduled publish or auto-publish-on-approval) is given up on after failing — terminally, or after exhausting retries. The draft is left open for a human to resolve.
constant.createdTriggered when a constant is created
constant.updatedTriggered when a constant is updated
constant.deletedTriggered when a constant is deleted
constant.revision.createdTriggered when a new draft revision is created for a constant
constant.revision.updatedTriggered when a draft revision's proposed changes are modified (value, archive, or metadata). The change field indicates the kind of mutation.
constant.revision.reviewRequestedTriggered when a draft revision is submitted for review
constant.revision.approvedTriggered when a draft revision is approved by a reviewer
constant.revision.changesRequestedTriggered when a reviewer requests changes on a draft revision
constant.revision.commentedTriggered when a comment is added to a draft revision
constant.revision.discardedTriggered when a draft revision is discarded
constant.revision.rebasedTriggered when a draft revision is rebased onto the latest live state
constant.revision.publishedTriggered when a draft revision is published. Overlaps with constant.updated but provides revision-specific context.
constant.revision.revertedTriggered when a constant is reverted to a previous published revision
constant.revision.reopenedTriggered when a discarded revision is reopened
constant.revision.publishFailedTriggered when a deferred publish (scheduled publish or auto-publish-on-approval) is given up on after failing — terminally, or after exhausting retries. The draft is left open for a human to resolve.
config.createdTriggered when a config is created
config.updatedTriggered when a config is updated
config.deletedTriggered when a config is deleted
config.revision.createdTriggered when a new draft revision is created for a config
config.revision.updatedTriggered when a draft revision's proposed changes are modified (value, schema, archive, or metadata). The change field indicates the kind of mutation.
config.revision.reviewRequestedTriggered when a draft revision is submitted for review
config.revision.approvedTriggered when a draft revision is approved by a reviewer
config.revision.changesRequestedTriggered when a reviewer requests changes on a draft revision
config.revision.commentedTriggered when a comment is added to a draft revision
config.revision.discardedTriggered when a draft revision is discarded
config.revision.rebasedTriggered when a draft revision is rebased onto the latest live state
config.revision.publishedTriggered when a draft revision is published. Overlaps with config.updated but provides revision-specific context.
config.revision.revertedTriggered when a config is reverted to a previous published revision
config.revision.reopenedTriggered when a discarded revision is reopened
config.revision.publishFailedTriggered when a deferred publish (scheduled publish or auto-publish-on-approval) is given up on after failing — terminally, or after exhausting retries. The draft is left open for a human to resolve.
user.loginTriggered when a user logs in

feature.created

Triggered when a feature is created

Payload
{
event: "feature.created";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id: string;
dateCreated: string;
dateUpdated: string;
archived: boolean;
description: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner: string;
project: string;
valueType: "boolean" | "string" | "number" | "json";
defaultValue: string;
tags: string[];
environments: Record<string, any>;
/** Feature IDs. Each feature must evaluate to `true` */
prerequisites?: string[] | undefined;
revision: {
version: number;
comment: string;
date: string;
createdBy: string;
publishedBy: string;
};
customFields?: Record<string, {}> | undefined;
holdout?: ({
/** Holdout ID */
id: string;
/** The feature value assigned to users in the holdout treatment group */
value: string;
} | null) | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.updated

Triggered when a feature is updated

Payload
{
event: "feature.updated";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id: string;
dateCreated: string;
dateUpdated: string;
archived: boolean;
description: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner: string;
project: string;
valueType: "boolean" | "string" | "number" | "json";
defaultValue: string;
tags: string[];
environments: Record<string, any>;
/** Feature IDs. Each feature must evaluate to `true` */
prerequisites?: string[] | undefined;
revision: {
version: number;
comment: string;
date: string;
createdBy: string;
publishedBy: string;
};
customFields?: Record<string, {}> | undefined;
holdout?: ({
/** Holdout ID */
id: string;
/** The feature value assigned to users in the holdout treatment group */
value: string;
} | null) | undefined;
};
previous_attributes: {
id?: string | undefined;
dateCreated?: string | undefined;
dateUpdated?: string | undefined;
archived?: boolean | undefined;
description?: string | undefined;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
project?: string | undefined;
valueType?: ("boolean" | "string" | "number" | "json") | undefined;
defaultValue?: string | undefined;
tags?: string[] | undefined;
environments?: Record<string, any> | undefined;
/** Feature IDs. Each feature must evaluate to `true` */
prerequisites?: string[] | undefined;
revision?: {
version: number;
comment: string;
date: string;
createdBy: string;
publishedBy: string;
} | undefined;
customFields?: Record<string, {}> | undefined;
holdout?: ({
/** Holdout ID */
id: string;
/** The feature value assigned to users in the holdout treatment group */
value: string;
} | null) | undefined;
};
changes?: {
added: Record<string, unknown>;
removed: Record<string, unknown>;
modified: Record<string, unknown>;
} | undefined;
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.deleted

Triggered when a feature is deleted

Payload
{
event: "feature.deleted";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id: string;
dateCreated: string;
dateUpdated: string;
archived: boolean;
description: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner: string;
project: string;
valueType: "boolean" | "string" | "number" | "json";
defaultValue: string;
tags: string[];
environments: Record<string, any>;
/** Feature IDs. Each feature must evaluate to `true` */
prerequisites?: string[] | undefined;
revision: {
version: number;
comment: string;
date: string;
createdBy: string;
publishedBy: string;
};
customFields?: Record<string, {}> | undefined;
holdout?: ({
/** Holdout ID */
id: string;
/** The feature value assigned to users in the holdout treatment group */
value: string;
} | null) | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.saferollout.ship

Triggered when a safe rollout is completed and safe to rollout to 100%.

Payload
{
event: "feature.saferollout.ship";
object: "feature";
api_version: string;
created: number;
data: {
object: {
featureId: string;
safeRolloutId: string;
environment: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.saferollout.rollback

Triggered when a safe rollout has a failing guardrail and should be reverted.

Payload
{
event: "feature.saferollout.rollback";
object: "feature";
api_version: string;
created: number;
data: {
object: {
featureId: string;
safeRolloutId: string;
environment: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.saferollout.unhealthy

Triggered when a safe rollout is failing a health check and may not be working as expected.

Payload
{
event: "feature.saferollout.unhealthy";
object: "feature";
api_version: string;
created: number;
data: {
object: {
featureId: string;
safeRolloutId: string;
environment: string;
unhealthyReason: ("srm" | "multipleExposures")[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.rampSchedule.created

Triggered when a ramp schedule is created for a feature

Payload
{
event: "feature.rampSchedule.created";
object: "feature";
api_version: string;
created: number;
data: {
object: {
rampScheduleId: string;
rampName: string;
orgId: string;
entityType: string;
entityId: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.rampSchedule.deleted

Triggered when a ramp schedule is deleted from a feature

Payload
{
event: "feature.rampSchedule.deleted";
object: "feature";
api_version: string;
created: number;
data: {
object: {
rampScheduleId: string;
rampName: string;
orgId: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.rampSchedule.actions.started

Triggered when a feature ramp schedule starts

Payload
{
event: "feature.rampSchedule.actions.started";
object: "feature";
api_version: string;
created: number;
data: {
object: {
rampScheduleId: string;
rampName: string;
orgId: string;
currentStepIndex: number;
status: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.rampSchedule.actions.completed

Triggered when a feature ramp schedule completes all steps

Payload
{
event: "feature.rampSchedule.actions.completed";
object: "feature";
api_version: string;
created: number;
data: {
object: {
rampScheduleId: string;
rampName: string;
orgId: string;
currentStepIndex: number;
status: string;
previousStepIndex?: number | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.rampSchedule.actions.rolledBack

Triggered when a feature ramp schedule is rolled back or reset to start

Payload
{
event: "feature.rampSchedule.actions.rolledBack";
object: "feature";
api_version: string;
created: number;
data: {
object: {
rampScheduleId: string;
rampName: string;
orgId: string;
currentStepIndex: number;
status: string;
targetStepIndex: number;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.rampSchedule.actions.jumped

Triggered when a feature ramp schedule is jumped to a specific step

Payload
{
event: "feature.rampSchedule.actions.jumped";
object: "feature";
api_version: string;
created: number;
data: {
object: {
rampScheduleId: string;
rampName: string;
orgId: string;
currentStepIndex: number;
status: string;
targetStepIndex: number;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.rampSchedule.actions.step.advanced

Triggered when a feature ramp schedule advances. Overdue steps are caught up in a single advance: when currentStepIndex - previousStepIndex > 1, the intermediate steps were folded into this one event (one revision publish) rather than fired individually.

Payload
{
event: "feature.rampSchedule.actions.step.advanced";
object: "feature";
api_version: string;
created: number;
data: {
object: {
rampScheduleId: string;
rampName: string;
orgId: string;
currentStepIndex: number;
status: string;
previousStepIndex?: number | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.rampSchedule.actions.step.approvalRequired

Triggered when a feature ramp step is waiting for approval

Payload
{
event: "feature.rampSchedule.actions.step.approvalRequired";
object: "feature";
api_version: string;
created: number;
data: {
object: {
rampScheduleId: string;
rampName: string;
orgId: string;
currentStepIndex: number;
status: string;
approvalNotes?: (string | null) | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.rampSchedule.actions.awaitingStartApproval

Triggered when a feature ramp schedule is published but held at the start, awaiting an explicit start approval

Payload
{
event: "feature.rampSchedule.actions.awaitingStartApproval";
object: "feature";
api_version: string;
created: number;
data: {
object: {
rampScheduleId: string;
rampName: string;
orgId: string;
currentStepIndex: number;
status: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.rampSchedule.actions.startApproved

Triggered when a held ramp schedule's start is approved by a user

Payload
{
event: "feature.rampSchedule.actions.startApproved";
object: "feature";
api_version: string;
created: number;
data: {
object: {
rampScheduleId: string;
rampName: string;
orgId: string;
currentStepIndex: number;
status: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.revision.created

Triggered when a new draft revision is created for a feature

Payload
{
event: "feature.revision.created";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id?: string | undefined;
/** The feature this revision belongs to */
featureId: string;
baseVersion: number;
version: number;
comment: string;
date: string;
status: string;
createdBy?: string | undefined;
publishedBy?: string | undefined;
/** The default value at the time this revision was created */
defaultValue?: string | undefined;
rules: Record<string, any[]>;
definitions?: Record<string, string> | undefined;
environmentsEnabled?: Record<string, boolean> | undefined;
envPrerequisites?: Record<string, {
/** Feature ID */
id: string;
condition: string;
}[]> | undefined;
prerequisites?: {
/** Feature ID */
id: string;
condition: string;
}[] | undefined;
metadata?: {} | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.revision.updated

Triggered when a draft revision is modified (rules, default value, toggles, prerequisites, metadata, etc.). The change field indicates the specific kind of mutation.

Payload
{
event: "feature.revision.updated";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id?: string | undefined;
/** The feature this revision belongs to */
featureId: string;
baseVersion: number;
version: number;
comment: string;
date: string;
status: string;
createdBy?: string | undefined;
publishedBy?: string | undefined;
/** The default value at the time this revision was created */
defaultValue?: string | undefined;
rules: Record<string, any[]>;
definitions?: Record<string, string> | undefined;
environmentsEnabled?: Record<string, boolean> | undefined;
envPrerequisites?: Record<string, {
/** Feature ID */
id: string;
condition: string;
}[]> | undefined;
prerequisites?: {
/** Feature ID */
id: string;
condition: string;
}[] | undefined;
metadata?: {} | undefined;
change: "rule.add" | "rule.update" | "rule.delete" | "rule.reorder" | "rule.rampSchedule.set" | "rule.rampSchedule.remove" | "toggle" | "defaultValue" | "prerequisites" | "holdout" | "archive" | "metadata";
environments?: string[] | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.revision.reviewRequested

Triggered when a draft revision is submitted for review

Payload
{
event: "feature.revision.reviewRequested";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id?: string | undefined;
/** The feature this revision belongs to */
featureId: string;
baseVersion: number;
version: number;
comment: string;
date: string;
status: string;
createdBy?: string | undefined;
publishedBy?: string | undefined;
/** The default value at the time this revision was created */
defaultValue?: string | undefined;
rules: Record<string, any[]>;
definitions?: Record<string, string> | undefined;
environmentsEnabled?: Record<string, boolean> | undefined;
envPrerequisites?: Record<string, {
/** Feature ID */
id: string;
condition: string;
}[]> | undefined;
prerequisites?: {
/** Feature ID */
id: string;
condition: string;
}[] | undefined;
metadata?: {} | undefined;
reviewComment: string | null;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.revision.approved

Triggered when a draft revision is approved by a reviewer

Payload
{
event: "feature.revision.approved";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id?: string | undefined;
/** The feature this revision belongs to */
featureId: string;
baseVersion: number;
version: number;
comment: string;
date: string;
status: string;
createdBy?: string | undefined;
publishedBy?: string | undefined;
/** The default value at the time this revision was created */
defaultValue?: string | undefined;
rules: Record<string, any[]>;
definitions?: Record<string, string> | undefined;
environmentsEnabled?: Record<string, boolean> | undefined;
envPrerequisites?: Record<string, {
/** Feature ID */
id: string;
condition: string;
}[]> | undefined;
prerequisites?: {
/** Feature ID */
id: string;
condition: string;
}[] | undefined;
metadata?: {} | undefined;
reviewer: {
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
};
reviewComment: string | null;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.revision.changesRequested

Triggered when a reviewer requests changes on a draft revision

Payload
{
event: "feature.revision.changesRequested";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id?: string | undefined;
/** The feature this revision belongs to */
featureId: string;
baseVersion: number;
version: number;
comment: string;
date: string;
status: string;
createdBy?: string | undefined;
publishedBy?: string | undefined;
/** The default value at the time this revision was created */
defaultValue?: string | undefined;
rules: Record<string, any[]>;
definitions?: Record<string, string> | undefined;
environmentsEnabled?: Record<string, boolean> | undefined;
envPrerequisites?: Record<string, {
/** Feature ID */
id: string;
condition: string;
}[]> | undefined;
prerequisites?: {
/** Feature ID */
id: string;
condition: string;
}[] | undefined;
metadata?: {} | undefined;
reviewer: {
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
};
reviewComment: string | null;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.revision.commented

Triggered when a comment is added to a draft revision

Payload
{
event: "feature.revision.commented";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id?: string | undefined;
/** The feature this revision belongs to */
featureId: string;
baseVersion: number;
version: number;
comment: string;
date: string;
status: string;
createdBy?: string | undefined;
publishedBy?: string | undefined;
/** The default value at the time this revision was created */
defaultValue?: string | undefined;
rules: Record<string, any[]>;
definitions?: Record<string, string> | undefined;
environmentsEnabled?: Record<string, boolean> | undefined;
envPrerequisites?: Record<string, {
/** Feature ID */
id: string;
condition: string;
}[]> | undefined;
prerequisites?: {
/** Feature ID */
id: string;
condition: string;
}[] | undefined;
metadata?: {} | undefined;
reviewer: {
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
};
reviewComment: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.revision.discarded

Triggered when a draft revision is discarded

Payload
{
event: "feature.revision.discarded";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id?: string | undefined;
/** The feature this revision belongs to */
featureId: string;
baseVersion: number;
version: number;
comment: string;
date: string;
status: string;
createdBy?: string | undefined;
publishedBy?: string | undefined;
/** The default value at the time this revision was created */
defaultValue?: string | undefined;
rules: Record<string, any[]>;
definitions?: Record<string, string> | undefined;
environmentsEnabled?: Record<string, boolean> | undefined;
envPrerequisites?: Record<string, {
/** Feature ID */
id: string;
condition: string;
}[]> | undefined;
prerequisites?: {
/** Feature ID */
id: string;
condition: string;
}[] | undefined;
metadata?: {} | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.revision.reopened

Triggered when a discarded draft revision is reopened as a draft

Payload
{
event: "feature.revision.reopened";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id?: string | undefined;
/** The feature this revision belongs to */
featureId: string;
baseVersion: number;
version: number;
comment: string;
date: string;
status: string;
createdBy?: string | undefined;
publishedBy?: string | undefined;
/** The default value at the time this revision was created */
defaultValue?: string | undefined;
rules: Record<string, any[]>;
definitions?: Record<string, string> | undefined;
environmentsEnabled?: Record<string, boolean> | undefined;
envPrerequisites?: Record<string, {
/** Feature ID */
id: string;
condition: string;
}[]> | undefined;
prerequisites?: {
/** Feature ID */
id: string;
condition: string;
}[] | undefined;
metadata?: {} | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.revision.rebased

Triggered when a draft revision is rebased onto the latest published version

Payload
{
event: "feature.revision.rebased";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id?: string | undefined;
/** The feature this revision belongs to */
featureId: string;
baseVersion: number;
version: number;
comment: string;
date: string;
status: string;
createdBy?: string | undefined;
publishedBy?: string | undefined;
/** The default value at the time this revision was created */
defaultValue?: string | undefined;
rules: Record<string, any[]>;
definitions?: Record<string, string> | undefined;
environmentsEnabled?: Record<string, boolean> | undefined;
envPrerequisites?: Record<string, {
/** Feature ID */
id: string;
condition: string;
}[]> | undefined;
prerequisites?: {
/** Feature ID */
id: string;
condition: string;
}[] | undefined;
metadata?: {} | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.revision.published

Triggered when a draft revision is published. Overlaps with feature.updated but provides revision-specific context (base version, comment, author).

Payload
{
event: "feature.revision.published";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id?: string | undefined;
/** The feature this revision belongs to */
featureId: string;
baseVersion: number;
version: number;
comment: string;
date: string;
status: string;
createdBy?: string | undefined;
publishedBy?: string | undefined;
/** The default value at the time this revision was created */
defaultValue?: string | undefined;
rules: Record<string, any[]>;
definitions?: Record<string, string> | undefined;
environmentsEnabled?: Record<string, boolean> | undefined;
envPrerequisites?: Record<string, {
/** Feature ID */
id: string;
condition: string;
}[]> | undefined;
prerequisites?: {
/** Feature ID */
id: string;
condition: string;
}[] | undefined;
metadata?: {} | undefined;
bulkPublishId?: string | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.revision.reverted

Triggered when a feature is reverted to a previous published revision

Payload
{
event: "feature.revision.reverted";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id?: string | undefined;
/** The feature this revision belongs to */
featureId: string;
baseVersion: number;
version: number;
comment: string;
date: string;
status: string;
createdBy?: string | undefined;
publishedBy?: string | undefined;
/** The default value at the time this revision was created */
defaultValue?: string | undefined;
rules: Record<string, any[]>;
definitions?: Record<string, string> | undefined;
environmentsEnabled?: Record<string, boolean> | undefined;
envPrerequisites?: Record<string, {
/** Feature ID */
id: string;
condition: string;
}[]> | undefined;
prerequisites?: {
/** Feature ID */
id: string;
condition: string;
}[] | undefined;
metadata?: {} | undefined;
revertedToVersion: number;
bulkPublishId?: string | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

feature.revision.publishFailed

Triggered when a deferred publish (scheduled publish or auto-publish-on-approval) is given up on after failing — terminally, or after exhausting retries. The draft is left open for a human to resolve.

Payload
{
event: "feature.revision.publishFailed";
object: "feature";
api_version: string;
created: number;
data: {
object: {
id?: string | undefined;
/** The feature this revision belongs to */
featureId: string;
baseVersion: number;
version: number;
comment: string;
date: string;
status: string;
createdBy?: string | undefined;
publishedBy?: string | undefined;
/** The default value at the time this revision was created */
defaultValue?: string | undefined;
rules: Record<string, any[]>;
definitions?: Record<string, string> | undefined;
environmentsEnabled?: Record<string, boolean> | undefined;
envPrerequisites?: Record<string, {
/** Feature ID */
id: string;
condition: string;
}[]> | undefined;
prerequisites?: {
/** Feature ID */
id: string;
condition: string;
}[] | undefined;
metadata?: {} | undefined;
bulkPublishId?: string | undefined;
failureReason: string;
terminal: boolean;
attempts: number;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

experiment.created

Triggered when an experiment is created

Payload
{
event: "experiment.created";
object: "experiment";
api_version: string;
created: number;
data: {
object: {
id: string;
trackingKey: string;
dateCreated: string;
dateUpdated: string;
name: string;
type: "standard" | "multi-armed-bandit" | "holdout";
project: string;
hypothesis: string;
description: string;
tags: string[];
/** The userId of the owner (or raw owner name/email for legacy records) */
owner: string;
ownerEmail?: string | undefined;
archived: boolean;
status: string;
autoRefresh: boolean;
hashAttribute: string;
fallbackAttribute?: string | undefined;
hashVersion: 1 | 2;
disableStickyBucketing?: boolean | undefined;
bucketVersion?: number | undefined;
minBucketVersion?: number | undefined;
variations: {
variationId: string;
key: string;
name: string;
description: string;
screenshots: string[];
}[];
phases: {
name: string;
dateStarted: string;
dateEnded: string;
reasonForStopping: string;
seed: string;
coverage: number;
trafficSplit: {
variationId: string;
weight: number;
}[];
namespace?: {
namespaceId: string;
enabled?: boolean | undefined;
range?: number[] | undefined;
ranges?: [
number,
number
][] | undefined;
} | undefined;
targetingCondition: string;
prerequisites?: {
id: string;
condition: string;
}[] | undefined;
savedGroupTargeting?: {
matchType: "all" | "any" | "none";
savedGroups: string[];
}[] | undefined;
}[];
settings: {
datasourceId: string;
assignmentQueryId: string;
experimentId: string;
segmentId: string;
queryFilter: string;
inProgressConversions: "include" | "exclude";
/** Setting attribution model to `"experimentDuration"` is the same as selecting "Ignore Conversion Windows" for the Conversion Window Override. Setting it to `"lookbackOverride"` requires a `lookbackOverride` object to be provided. */
attributionModel: "firstExposure" | "experimentDuration" | "lookbackOverride";
/** Controls the lookback override for the experiment. For type "window", value must be a non-negative number and valueUnit is required. */
lookbackOverride?: {
type: "date" | "window";
/** For "window" type - non-negative numeric value (e.g. 7 for 7 days). For "date" type a date string. */
value: number | string;
/** Used when type is "window". Defaults to "days". */
valueUnit?: ("minutes" | "hours" | "days" | "weeks") | undefined;
} | undefined;
statsEngine: "bayesian" | "frequentist";
regressionAdjustmentEnabled?: boolean | undefined;
sequentialTestingEnabled?: boolean | undefined;
sequentialTestingTuningParameter?: number | undefined;
/** When null, the organization default is used. */
postStratificationEnabled?: (boolean | null) | undefined;
/** Controls the decision framework and metric overrides for the experiment. Replaces the entire stored object on update (does not patch individual fields). */
decisionFrameworkSettings?: {
decisionCriteriaId?: string | undefined;
decisionFrameworkMetricOverrides?: {
/** ID of the metric to override settings for. */
id: string;
/** The target relative MDE to use for the metric, expressed as proportions (e.g. use 0.1 for 10%). Must be greater than 0. */
targetMDE?: number | undefined;
}[] | undefined;
} | undefined;
/** Per-metric analysis overrides; also reflected in goals/secondaryMetrics/guardrails overrides when applicable. On create/update, this replaces the entire stored array (it does not patch individual entries). */
metricOverrides?: {
/** ID of the metric to override settings for. */
id: string;
windowType?: ("conversion" | "lookback" | "") | undefined;
windowHours?: number | undefined;
delayHours?: number | undefined;
/** Must be true for the override to take effect. If true, the other proper prior settings in this object will be used if present. */
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
/** Must be true for the override to take effect. If true, the other regression adjustment settings in this object will be used if present. */
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
}[] | undefined;
goals: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
}[];
secondaryMetrics: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
}[];
guardrails: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
}[];
activationMetric?: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
} | undefined;
};
resultSummary?: {
status: string;
winner: string;
conclusions: string;
releasedVariationId: string;
excludeFromPayload: boolean;
} | undefined;
shareLevel?: ("public" | "organization") | undefined;
publicUrl?: string | undefined;
banditScheduleValue?: number | undefined;
banditScheduleUnit?: ("days" | "hours") | undefined;
banditBurnInValue?: number | undefined;
banditBurnInUnit?: ("days" | "hours") | undefined;
banditConversionWindowValue?: number | undefined;
banditConversionWindowUnit?: ("days" | "hours") | undefined;
linkedFeatures?: string[] | undefined;
hasVisualChangesets?: boolean | undefined;
hasURLRedirects?: boolean | undefined;
customFields?: Record<string, any> | undefined;
/** Custom slices that apply to ALL applicable metrics in the experiment */
customMetricSlices?: {
slices: {
column: string;
levels: string[];
}[];
}[] | undefined;
precomputedUnitDimensionIds?: string[] | undefined;
/** ID of the default dashboard for this experiment. */
defaultDashboardId?: string | undefined;
templateId?: string | undefined;
statusUpdateSchedule?: ({
startAt?: string | undefined;
stopAt?: string | undefined;
/** Relative end offset. Deferred: resolved to a concrete `stopAt` at the experiment's actual start (or off `dateStarted` when already running). */
stopAfter?: {
value: number;
unit: "hours" | "days";
} | undefined;
/** What happens at the scheduled end date. `notify` keeps the experiment running and just notifies (soft). `auto-ship` (requires the Decision Framework) ships the winning variation and stops; multi-winner ties break on `tiebreakerMetricId` (higher lift); with no clear winner, `fallback` either keeps running (`notify`) or ships `fallbackVariationId`. `force-ship` stops and rolls out `fallbackVariationId`. `stop` is a hard deadline that stops with no rollout. For `force-ship` and `stop`, the Decision Framework verdict (won/lost/inconclusive) is recorded as metadata when available. */
scheduledStopPlan?: {
mode: "notify" | "auto-ship" | "force-ship" | "stop";
tiebreakerMetricId?: string | undefined;
fallback: "notify" | "force-ship";
fallbackVariationId?: string | undefined;
} | undefined;
} | null) | undefined;
nextScheduledStatusUpdate?: ({
type: "start" | "stop";
date: string;
} | null) | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

experiment.updated

Triggered when an experiment is updated

Payload
{
event: "experiment.updated";
object: "experiment";
api_version: string;
created: number;
data: {
object: {
id: string;
trackingKey: string;
dateCreated: string;
dateUpdated: string;
name: string;
type: "standard" | "multi-armed-bandit" | "holdout";
project: string;
hypothesis: string;
description: string;
tags: string[];
/** The userId of the owner (or raw owner name/email for legacy records) */
owner: string;
ownerEmail?: string | undefined;
archived: boolean;
status: string;
autoRefresh: boolean;
hashAttribute: string;
fallbackAttribute?: string | undefined;
hashVersion: 1 | 2;
disableStickyBucketing?: boolean | undefined;
bucketVersion?: number | undefined;
minBucketVersion?: number | undefined;
variations: {
variationId: string;
key: string;
name: string;
description: string;
screenshots: string[];
}[];
phases: {
name: string;
dateStarted: string;
dateEnded: string;
reasonForStopping: string;
seed: string;
coverage: number;
trafficSplit: {
variationId: string;
weight: number;
}[];
namespace?: {
namespaceId: string;
enabled?: boolean | undefined;
range?: number[] | undefined;
ranges?: [
number,
number
][] | undefined;
} | undefined;
targetingCondition: string;
prerequisites?: {
id: string;
condition: string;
}[] | undefined;
savedGroupTargeting?: {
matchType: "all" | "any" | "none";
savedGroups: string[];
}[] | undefined;
}[];
settings: {
datasourceId: string;
assignmentQueryId: string;
experimentId: string;
segmentId: string;
queryFilter: string;
inProgressConversions: "include" | "exclude";
/** Setting attribution model to `"experimentDuration"` is the same as selecting "Ignore Conversion Windows" for the Conversion Window Override. Setting it to `"lookbackOverride"` requires a `lookbackOverride` object to be provided. */
attributionModel: "firstExposure" | "experimentDuration" | "lookbackOverride";
/** Controls the lookback override for the experiment. For type "window", value must be a non-negative number and valueUnit is required. */
lookbackOverride?: {
type: "date" | "window";
/** For "window" type - non-negative numeric value (e.g. 7 for 7 days). For "date" type a date string. */
value: number | string;
/** Used when type is "window". Defaults to "days". */
valueUnit?: ("minutes" | "hours" | "days" | "weeks") | undefined;
} | undefined;
statsEngine: "bayesian" | "frequentist";
regressionAdjustmentEnabled?: boolean | undefined;
sequentialTestingEnabled?: boolean | undefined;
sequentialTestingTuningParameter?: number | undefined;
/** When null, the organization default is used. */
postStratificationEnabled?: (boolean | null) | undefined;
/** Controls the decision framework and metric overrides for the experiment. Replaces the entire stored object on update (does not patch individual fields). */
decisionFrameworkSettings?: {
decisionCriteriaId?: string | undefined;
decisionFrameworkMetricOverrides?: {
/** ID of the metric to override settings for. */
id: string;
/** The target relative MDE to use for the metric, expressed as proportions (e.g. use 0.1 for 10%). Must be greater than 0. */
targetMDE?: number | undefined;
}[] | undefined;
} | undefined;
/** Per-metric analysis overrides; also reflected in goals/secondaryMetrics/guardrails overrides when applicable. On create/update, this replaces the entire stored array (it does not patch individual entries). */
metricOverrides?: {
/** ID of the metric to override settings for. */
id: string;
windowType?: ("conversion" | "lookback" | "") | undefined;
windowHours?: number | undefined;
delayHours?: number | undefined;
/** Must be true for the override to take effect. If true, the other proper prior settings in this object will be used if present. */
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
/** Must be true for the override to take effect. If true, the other regression adjustment settings in this object will be used if present. */
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
}[] | undefined;
goals: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
}[];
secondaryMetrics: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
}[];
guardrails: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
}[];
activationMetric?: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
} | undefined;
};
resultSummary?: {
status: string;
winner: string;
conclusions: string;
releasedVariationId: string;
excludeFromPayload: boolean;
} | undefined;
shareLevel?: ("public" | "organization") | undefined;
publicUrl?: string | undefined;
banditScheduleValue?: number | undefined;
banditScheduleUnit?: ("days" | "hours") | undefined;
banditBurnInValue?: number | undefined;
banditBurnInUnit?: ("days" | "hours") | undefined;
banditConversionWindowValue?: number | undefined;
banditConversionWindowUnit?: ("days" | "hours") | undefined;
linkedFeatures?: string[] | undefined;
hasVisualChangesets?: boolean | undefined;
hasURLRedirects?: boolean | undefined;
customFields?: Record<string, any> | undefined;
/** Custom slices that apply to ALL applicable metrics in the experiment */
customMetricSlices?: {
slices: {
column: string;
levels: string[];
}[];
}[] | undefined;
precomputedUnitDimensionIds?: string[] | undefined;
/** ID of the default dashboard for this experiment. */
defaultDashboardId?: string | undefined;
templateId?: string | undefined;
statusUpdateSchedule?: ({
startAt?: string | undefined;
stopAt?: string | undefined;
/** Relative end offset. Deferred: resolved to a concrete `stopAt` at the experiment's actual start (or off `dateStarted` when already running). */
stopAfter?: {
value: number;
unit: "hours" | "days";
} | undefined;
/** What happens at the scheduled end date. `notify` keeps the experiment running and just notifies (soft). `auto-ship` (requires the Decision Framework) ships the winning variation and stops; multi-winner ties break on `tiebreakerMetricId` (higher lift); with no clear winner, `fallback` either keeps running (`notify`) or ships `fallbackVariationId`. `force-ship` stops and rolls out `fallbackVariationId`. `stop` is a hard deadline that stops with no rollout. For `force-ship` and `stop`, the Decision Framework verdict (won/lost/inconclusive) is recorded as metadata when available. */
scheduledStopPlan?: {
mode: "notify" | "auto-ship" | "force-ship" | "stop";
tiebreakerMetricId?: string | undefined;
fallback: "notify" | "force-ship";
fallbackVariationId?: string | undefined;
} | undefined;
} | null) | undefined;
nextScheduledStatusUpdate?: ({
type: "start" | "stop";
date: string;
} | null) | undefined;
};
previous_attributes: {
id?: string | undefined;
trackingKey?: string | undefined;
dateCreated?: string | undefined;
dateUpdated?: string | undefined;
name?: string | undefined;
type?: ("standard" | "multi-armed-bandit" | "holdout") | undefined;
project?: string | undefined;
hypothesis?: string | undefined;
description?: string | undefined;
tags?: string[] | undefined;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
archived?: boolean | undefined;
status?: string | undefined;
autoRefresh?: boolean | undefined;
hashAttribute?: string | undefined;
fallbackAttribute?: string | undefined;
hashVersion?: (1 | 2) | undefined;
disableStickyBucketing?: boolean | undefined;
bucketVersion?: number | undefined;
minBucketVersion?: number | undefined;
variations?: {
variationId: string;
key: string;
name: string;
description: string;
screenshots: string[];
}[] | undefined;
phases?: {
name: string;
dateStarted: string;
dateEnded: string;
reasonForStopping: string;
seed: string;
coverage: number;
trafficSplit: {
variationId: string;
weight: number;
}[];
namespace?: {
namespaceId: string;
enabled?: boolean | undefined;
range?: number[] | undefined;
ranges?: [
number,
number
][] | undefined;
} | undefined;
targetingCondition: string;
prerequisites?: {
id: string;
condition: string;
}[] | undefined;
savedGroupTargeting?: {
matchType: "all" | "any" | "none";
savedGroups: string[];
}[] | undefined;
}[] | undefined;
settings?: {
datasourceId: string;
assignmentQueryId: string;
experimentId: string;
segmentId: string;
queryFilter: string;
inProgressConversions: "include" | "exclude";
/** Setting attribution model to `"experimentDuration"` is the same as selecting "Ignore Conversion Windows" for the Conversion Window Override. Setting it to `"lookbackOverride"` requires a `lookbackOverride` object to be provided. */
attributionModel: "firstExposure" | "experimentDuration" | "lookbackOverride";
/** Controls the lookback override for the experiment. For type "window", value must be a non-negative number and valueUnit is required. */
lookbackOverride?: {
type: "date" | "window";
/** For "window" type - non-negative numeric value (e.g. 7 for 7 days). For "date" type a date string. */
value: number | string;
/** Used when type is "window". Defaults to "days". */
valueUnit?: ("minutes" | "hours" | "days" | "weeks") | undefined;
} | undefined;
statsEngine: "bayesian" | "frequentist";
regressionAdjustmentEnabled?: boolean | undefined;
sequentialTestingEnabled?: boolean | undefined;
sequentialTestingTuningParameter?: number | undefined;
/** When null, the organization default is used. */
postStratificationEnabled?: (boolean | null) | undefined;
/** Controls the decision framework and metric overrides for the experiment. Replaces the entire stored object on update (does not patch individual fields). */
decisionFrameworkSettings?: {
decisionCriteriaId?: string | undefined;
decisionFrameworkMetricOverrides?: {
/** ID of the metric to override settings for. */
id: string;
/** The target relative MDE to use for the metric, expressed as proportions (e.g. use 0.1 for 10%). Must be greater than 0. */
targetMDE?: number | undefined;
}[] | undefined;
} | undefined;
/** Per-metric analysis overrides; also reflected in goals/secondaryMetrics/guardrails overrides when applicable. On create/update, this replaces the entire stored array (it does not patch individual entries). */
metricOverrides?: {
/** ID of the metric to override settings for. */
id: string;
windowType?: ("conversion" | "lookback" | "") | undefined;
windowHours?: number | undefined;
delayHours?: number | undefined;
/** Must be true for the override to take effect. If true, the other proper prior settings in this object will be used if present. */
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
/** Must be true for the override to take effect. If true, the other regression adjustment settings in this object will be used if present. */
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
}[] | undefined;
goals: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
}[];
secondaryMetrics: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
}[];
guardrails: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
}[];
activationMetric?: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
} | undefined;
} | undefined;
resultSummary?: {
status: string;
winner: string;
conclusions: string;
releasedVariationId: string;
excludeFromPayload: boolean;
} | undefined;
shareLevel?: ("public" | "organization") | undefined;
publicUrl?: string | undefined;
banditScheduleValue?: number | undefined;
banditScheduleUnit?: ("days" | "hours") | undefined;
banditBurnInValue?: number | undefined;
banditBurnInUnit?: ("days" | "hours") | undefined;
banditConversionWindowValue?: number | undefined;
banditConversionWindowUnit?: ("days" | "hours") | undefined;
linkedFeatures?: string[] | undefined;
hasVisualChangesets?: boolean | undefined;
hasURLRedirects?: boolean | undefined;
customFields?: Record<string, any> | undefined;
/** Custom slices that apply to ALL applicable metrics in the experiment */
customMetricSlices?: {
slices: {
column: string;
levels: string[];
}[];
}[] | undefined;
precomputedUnitDimensionIds?: string[] | undefined;
/** ID of the default dashboard for this experiment. */
defaultDashboardId?: string | undefined;
templateId?: string | undefined;
statusUpdateSchedule?: ({
startAt?: string | undefined;
stopAt?: string | undefined;
/** Relative end offset. Deferred: resolved to a concrete `stopAt` at the experiment's actual start (or off `dateStarted` when already running). */
stopAfter?: {
value: number;
unit: "hours" | "days";
} | undefined;
/** What happens at the scheduled end date. `notify` keeps the experiment running and just notifies (soft). `auto-ship` (requires the Decision Framework) ships the winning variation and stops; multi-winner ties break on `tiebreakerMetricId` (higher lift); with no clear winner, `fallback` either keeps running (`notify`) or ships `fallbackVariationId`. `force-ship` stops and rolls out `fallbackVariationId`. `stop` is a hard deadline that stops with no rollout. For `force-ship` and `stop`, the Decision Framework verdict (won/lost/inconclusive) is recorded as metadata when available. */
scheduledStopPlan?: {
mode: "notify" | "auto-ship" | "force-ship" | "stop";
tiebreakerMetricId?: string | undefined;
fallback: "notify" | "force-ship";
fallbackVariationId?: string | undefined;
} | undefined;
} | null) | undefined;
nextScheduledStatusUpdate?: ({
type: "start" | "stop";
date: string;
} | null) | undefined;
};
changes?: {
added: Record<string, unknown>;
removed: Record<string, unknown>;
modified: Record<string, unknown>;
} | undefined;
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

experiment.deleted

Triggered when an experiment is deleted

Payload
{
event: "experiment.deleted";
object: "experiment";
api_version: string;
created: number;
data: {
object: {
id: string;
trackingKey: string;
dateCreated: string;
dateUpdated: string;
name: string;
type: "standard" | "multi-armed-bandit" | "holdout";
project: string;
hypothesis: string;
description: string;
tags: string[];
/** The userId of the owner (or raw owner name/email for legacy records) */
owner: string;
ownerEmail?: string | undefined;
archived: boolean;
status: string;
autoRefresh: boolean;
hashAttribute: string;
fallbackAttribute?: string | undefined;
hashVersion: 1 | 2;
disableStickyBucketing?: boolean | undefined;
bucketVersion?: number | undefined;
minBucketVersion?: number | undefined;
variations: {
variationId: string;
key: string;
name: string;
description: string;
screenshots: string[];
}[];
phases: {
name: string;
dateStarted: string;
dateEnded: string;
reasonForStopping: string;
seed: string;
coverage: number;
trafficSplit: {
variationId: string;
weight: number;
}[];
namespace?: {
namespaceId: string;
enabled?: boolean | undefined;
range?: number[] | undefined;
ranges?: [
number,
number
][] | undefined;
} | undefined;
targetingCondition: string;
prerequisites?: {
id: string;
condition: string;
}[] | undefined;
savedGroupTargeting?: {
matchType: "all" | "any" | "none";
savedGroups: string[];
}[] | undefined;
}[];
settings: {
datasourceId: string;
assignmentQueryId: string;
experimentId: string;
segmentId: string;
queryFilter: string;
inProgressConversions: "include" | "exclude";
/** Setting attribution model to `"experimentDuration"` is the same as selecting "Ignore Conversion Windows" for the Conversion Window Override. Setting it to `"lookbackOverride"` requires a `lookbackOverride` object to be provided. */
attributionModel: "firstExposure" | "experimentDuration" | "lookbackOverride";
/** Controls the lookback override for the experiment. For type "window", value must be a non-negative number and valueUnit is required. */
lookbackOverride?: {
type: "date" | "window";
/** For "window" type - non-negative numeric value (e.g. 7 for 7 days). For "date" type a date string. */
value: number | string;
/** Used when type is "window". Defaults to "days". */
valueUnit?: ("minutes" | "hours" | "days" | "weeks") | undefined;
} | undefined;
statsEngine: "bayesian" | "frequentist";
regressionAdjustmentEnabled?: boolean | undefined;
sequentialTestingEnabled?: boolean | undefined;
sequentialTestingTuningParameter?: number | undefined;
/** When null, the organization default is used. */
postStratificationEnabled?: (boolean | null) | undefined;
/** Controls the decision framework and metric overrides for the experiment. Replaces the entire stored object on update (does not patch individual fields). */
decisionFrameworkSettings?: {
decisionCriteriaId?: string | undefined;
decisionFrameworkMetricOverrides?: {
/** ID of the metric to override settings for. */
id: string;
/** The target relative MDE to use for the metric, expressed as proportions (e.g. use 0.1 for 10%). Must be greater than 0. */
targetMDE?: number | undefined;
}[] | undefined;
} | undefined;
/** Per-metric analysis overrides; also reflected in goals/secondaryMetrics/guardrails overrides when applicable. On create/update, this replaces the entire stored array (it does not patch individual entries). */
metricOverrides?: {
/** ID of the metric to override settings for. */
id: string;
windowType?: ("conversion" | "lookback" | "") | undefined;
windowHours?: number | undefined;
delayHours?: number | undefined;
/** Must be true for the override to take effect. If true, the other proper prior settings in this object will be used if present. */
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
/** Must be true for the override to take effect. If true, the other regression adjustment settings in this object will be used if present. */
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
}[] | undefined;
goals: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
}[];
secondaryMetrics: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
}[];
guardrails: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
}[];
activationMetric?: {
metricId: string;
overrides: {
delayHours?: number | undefined;
windowHours?: number | undefined;
window?: ("conversion" | "lookback" | "") | undefined;
winRiskThreshold?: number | undefined;
loseRiskThreshold?: number | undefined;
properPriorOverride?: boolean | undefined;
properPriorEnabled?: boolean | undefined;
properPriorMean?: number | undefined;
properPriorStdDev?: number | undefined;
regressionAdjustmentOverride?: boolean | undefined;
regressionAdjustmentEnabled?: boolean | undefined;
regressionAdjustmentDays?: number | undefined;
};
} | undefined;
};
resultSummary?: {
status: string;
winner: string;
conclusions: string;
releasedVariationId: string;
excludeFromPayload: boolean;
} | undefined;
shareLevel?: ("public" | "organization") | undefined;
publicUrl?: string | undefined;
banditScheduleValue?: number | undefined;
banditScheduleUnit?: ("days" | "hours") | undefined;
banditBurnInValue?: number | undefined;
banditBurnInUnit?: ("days" | "hours") | undefined;
banditConversionWindowValue?: number | undefined;
banditConversionWindowUnit?: ("days" | "hours") | undefined;
linkedFeatures?: string[] | undefined;
hasVisualChangesets?: boolean | undefined;
hasURLRedirects?: boolean | undefined;
customFields?: Record<string, any> | undefined;
/** Custom slices that apply to ALL applicable metrics in the experiment */
customMetricSlices?: {
slices: {
column: string;
levels: string[];
}[];
}[] | undefined;
precomputedUnitDimensionIds?: string[] | undefined;
/** ID of the default dashboard for this experiment. */
defaultDashboardId?: string | undefined;
templateId?: string | undefined;
statusUpdateSchedule?: ({
startAt?: string | undefined;
stopAt?: string | undefined;
/** Relative end offset. Deferred: resolved to a concrete `stopAt` at the experiment's actual start (or off `dateStarted` when already running). */
stopAfter?: {
value: number;
unit: "hours" | "days";
} | undefined;
/** What happens at the scheduled end date. `notify` keeps the experiment running and just notifies (soft). `auto-ship` (requires the Decision Framework) ships the winning variation and stops; multi-winner ties break on `tiebreakerMetricId` (higher lift); with no clear winner, `fallback` either keeps running (`notify`) or ships `fallbackVariationId`. `force-ship` stops and rolls out `fallbackVariationId`. `stop` is a hard deadline that stops with no rollout. For `force-ship` and `stop`, the Decision Framework verdict (won/lost/inconclusive) is recorded as metadata when available. */
scheduledStopPlan?: {
mode: "notify" | "auto-ship" | "force-ship" | "stop";
tiebreakerMetricId?: string | undefined;
fallback: "notify" | "force-ship";
fallbackVariationId?: string | undefined;
} | undefined;
} | null) | undefined;
nextScheduledStatusUpdate?: ({
type: "start" | "stop";
date: string;
} | null) | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

experiment.warning

Triggered when a warning condition is detected on an experiment

Payload
{
event: "experiment.warning";
object: "experiment";
api_version: string;
created: number;
data: {
object: {
type: "auto-update";
success: boolean;
experimentName: string;
experimentId: string;
} | {
type: "multiple-exposures";
experimentName: string;
experimentId: string;
usersCount: number;
percent: number;
} | {
type: "srm";
experimentName: string;
experimentId: string;
threshold: number;
} | {
type: "no-data";
experimentName: string;
experimentId: string;
} | {
type: "scheduled-status-update-failed";
experimentName: string;
experimentId: string;
scheduledStatusUpdateType: "start" | "stop";
attempts: number;
maxAttempts: number;
willRetry: boolean;
reason: string;
} | {
type: "underpowered";
experimentName: string;
experimentId: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

experiment.info.significance

Triggered when a goal or guardrail metric reaches significance in an experiment (e.g. either above 95% or below 5% chance to win). Be careful using this without Sequential Testing as it can lead to peeking problems.

Payload
{
event: "experiment.info.significance";
object: "experiment";
api_version: string;
created: number;
data: {
object: {
experimentName: string;
experimentId: string;
variationId: string;
variationName: string;
metricName: string;
metricId: string;
statsEngine: string;
criticalValue: number;
winning: boolean;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

experiment.info.scheduled-status-update

Triggered when a scheduled start or stop is automatically applied to an experiment, including the auto-ship outcome for a scheduled end.

Payload
{
event: "experiment.info.scheduled-status-update";
object: "experiment";
api_version: string;
created: number;
data: {
object: {
experimentId: string;
experimentName: string;
action: "started" | "stopped" | "kept-running";
shipped?: boolean | undefined;
shippedVariationId?: string | undefined;
shippedVariationName?: string | undefined;
forced?: boolean | undefined;
recommendedVariationId?: string | undefined;
recommendedVariationName?: string | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

experiment.decision.ship

Triggered when an experiment is ready to ship a variation.

Payload
{
event: "experiment.decision.ship";
object: "experiment";
api_version: string;
created: number;
data: {
object: {
experimentName: string;
experimentId: string;
decisionDescription?: string | undefined;
source: "scheduled-end" | "analysis";
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

experiment.decision.rollback

Triggered when an experiment should be rolled back to the control.

Payload
{
event: "experiment.decision.rollback";
object: "experiment";
api_version: string;
created: number;
data: {
object: {
experimentName: string;
experimentId: string;
decisionDescription?: string | undefined;
source: "scheduled-end" | "analysis";
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

experiment.decision.review

Triggered when an experiment has reached the desired power point, but the results may be ambiguous.

Payload
{
event: "experiment.decision.review";
object: "experiment";
api_version: string;
created: number;
data: {
object: {
experimentName: string;
experimentId: string;
decisionDescription?: string | undefined;
source: "scheduled-end" | "analysis";
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.created

Triggered when a saved group is created

Payload
{
event: "savedGroup.created";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.updated

Triggered when a saved group is updated

Payload
{
event: "savedGroup.updated";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
previous_attributes: {
id?: string | undefined;
type?: ("condition" | "list") | undefined;
dateCreated?: string | undefined;
dateUpdated?: string | undefined;
name?: string | undefined;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
changes?: {
added: Record<string, unknown>;
removed: Record<string, unknown>;
modified: Record<string, unknown>;
} | undefined;
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.deleted

Triggered when a saved group is deleted

Payload
{
event: "savedGroup.deleted";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.revision.created

Triggered when a new draft revision is created for a saved group

Payload
{
event: "savedGroup.revision.created";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.revision.updated

Triggered when a draft revision's proposed changes are modified (values, condition, archive, or metadata). The change field indicates the kind of mutation.

Payload
{
event: "savedGroup.revision.updated";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedChanges: {
op: string;
path: string;
}[];
change: "metadata" | "condition" | "values" | "archive";
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.revision.reviewRequested

Triggered when a draft revision is submitted for review

Payload
{
event: "savedGroup.revision.reviewRequested";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.revision.approved

Triggered when a draft revision is approved by a reviewer

Payload
{
event: "savedGroup.revision.approved";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedChanges: {
op: string;
path: string;
}[];
reviewer: {
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
};
reviewComment: string | null;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.revision.changesRequested

Triggered when a reviewer requests changes on a draft revision

Payload
{
event: "savedGroup.revision.changesRequested";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedChanges: {
op: string;
path: string;
}[];
reviewer: {
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
};
reviewComment: string | null;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.revision.commented

Triggered when a comment is added to a draft revision

Payload
{
event: "savedGroup.revision.commented";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedChanges: {
op: string;
path: string;
}[];
reviewer: {
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
};
reviewComment: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.revision.discarded

Triggered when a draft revision is discarded

Payload
{
event: "savedGroup.revision.discarded";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.revision.rebased

Triggered when a draft revision is rebased onto the latest live state

Payload
{
event: "savedGroup.revision.rebased";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.revision.published

Triggered when a draft revision is published. Overlaps with savedGroup.updated but provides revision-specific context.

Payload
{
event: "savedGroup.revision.published";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedChanges: {
op: string;
path: string;
}[];
bulkPublishId?: string | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.revision.reverted

Triggered when a saved group is reverted to a previous published revision

Payload
{
event: "savedGroup.revision.reverted";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedChanges: {
op: string;
path: string;
}[];
revertedToVersion?: number | undefined;
bulkPublishId?: string | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.revision.reopened

Triggered when a discarded revision is reopened

Payload
{
event: "savedGroup.revision.reopened";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

savedGroup.revision.publishFailed

Triggered when a deferred publish (scheduled publish or auto-publish-on-approval) is given up on after failing — terminally, or after exhausting retries. The draft is left open for a human to resolve.

Payload
{
event: "savedGroup.revision.publishFailed";
object: "savedGroup";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedSavedGroup: {
id: string;
type: "condition" | "list";
dateCreated: string;
dateUpdated: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** When type = 'condition', this is the JSON-encoded condition for the group */
condition?: string | undefined;
/** When type = 'list', this is the attribute key the group is based on */
attributeKey?: string | undefined;
/** When type = 'list', this is the list of values for the attribute key */
values?: string[] | undefined;
description?: string | undefined;
projects?: string[] | undefined;
archived?: boolean | undefined;
useEmptyListGroup?: boolean | undefined;
};
proposedChanges: {
op: string;
path: string;
}[];
bulkPublishId?: string | undefined;
failureReason: string;
terminal: boolean;
attempts: number;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.created

Triggered when a constant is created

Payload
{
event: "constant.created";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.updated

Triggered when a constant is updated

Payload
{
event: "constant.updated";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
previous_attributes: {
id?: string | undefined;
/** Stable reference handle; used as `@const:key` in values */
key?: string | undefined;
name?: string | undefined;
type?: ("string" | "json") | undefined;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated?: string | undefined;
dateUpdated?: string | undefined;
};
changes?: {
added: Record<string, unknown>;
removed: Record<string, unknown>;
modified: Record<string, unknown>;
} | undefined;
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.deleted

Triggered when a constant is deleted

Payload
{
event: "constant.deleted";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.revision.created

Triggered when a new draft revision is created for a constant

Payload
{
event: "constant.revision.created";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.revision.updated

Triggered when a draft revision's proposed changes are modified (value, archive, or metadata). The change field indicates the kind of mutation.

Payload
{
event: "constant.revision.updated";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
change: "metadata" | "value" | "archive";
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.revision.reviewRequested

Triggered when a draft revision is submitted for review

Payload
{
event: "constant.revision.reviewRequested";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.revision.approved

Triggered when a draft revision is approved by a reviewer

Payload
{
event: "constant.revision.approved";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
reviewer: {
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
};
reviewComment: string | null;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.revision.changesRequested

Triggered when a reviewer requests changes on a draft revision

Payload
{
event: "constant.revision.changesRequested";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
reviewer: {
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
};
reviewComment: string | null;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.revision.commented

Triggered when a comment is added to a draft revision

Payload
{
event: "constant.revision.commented";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
reviewer: {
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
};
reviewComment: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.revision.discarded

Triggered when a draft revision is discarded

Payload
{
event: "constant.revision.discarded";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.revision.rebased

Triggered when a draft revision is rebased onto the latest live state

Payload
{
event: "constant.revision.rebased";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.revision.published

Triggered when a draft revision is published. Overlaps with constant.updated but provides revision-specific context.

Payload
{
event: "constant.revision.published";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
bulkPublishId?: string | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.revision.reverted

Triggered when a constant is reverted to a previous published revision

Payload
{
event: "constant.revision.reverted";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
revertedToVersion?: number | undefined;
bulkPublishId?: string | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.revision.reopened

Triggered when a discarded revision is reopened

Payload
{
event: "constant.revision.reopened";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

constant.revision.publishFailed

Triggered when a deferred publish (scheduled publish or auto-publish-on-approval) is given up on after failing — terminally, or after exhausting retries. The draft is left open for a human to resolve.

Payload
{
event: "constant.revision.publishFailed";
object: "constant";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConstant: {
id: string;
/** Stable reference handle; used as `@const:key` in values */
key: string;
name: string;
type: "string" | "json";
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The default value (raw string for `string` constants, JSON-encoded for `json` constants) */
value?: string | undefined;
/** Per-environment value overrides (environment id → value). Falls back to `value` when an environment is absent. */
environmentValues?: Record<string, string> | undefined;
description?: string | undefined;
/** The project this constant belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
bulkPublishId?: string | undefined;
failureReason: string;
terminal: boolean;
attempts: number;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.created

Triggered when a config is created

Payload
{
event: "config.created";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.updated

Triggered when a config is updated

Payload
{
event: "config.updated";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
previous_attributes: {
id?: string | undefined;
/** Stable reference handle; used as `@config:key` in values */
key?: string | undefined;
name?: string | undefined;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated?: string | undefined;
dateUpdated?: string | undefined;
};
changes?: {
added: Record<string, unknown>;
removed: Record<string, unknown>;
modified: Record<string, unknown>;
} | undefined;
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.deleted

Triggered when a config is deleted

Payload
{
event: "config.deleted";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.revision.created

Triggered when a new draft revision is created for a config

Payload
{
event: "config.revision.created";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.revision.updated

Triggered when a draft revision's proposed changes are modified (value, schema, archive, or metadata). The change field indicates the kind of mutation.

Payload
{
event: "config.revision.updated";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
change: "metadata" | "value" | "schema" | "archive";
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.revision.reviewRequested

Triggered when a draft revision is submitted for review

Payload
{
event: "config.revision.reviewRequested";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.revision.approved

Triggered when a draft revision is approved by a reviewer

Payload
{
event: "config.revision.approved";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
reviewer: {
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
};
reviewComment: string | null;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.revision.changesRequested

Triggered when a reviewer requests changes on a draft revision

Payload
{
event: "config.revision.changesRequested";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
reviewer: {
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
};
reviewComment: string | null;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.revision.commented

Triggered when a comment is added to a draft revision

Payload
{
event: "config.revision.commented";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
reviewer: {
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
};
reviewComment: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.revision.discarded

Triggered when a draft revision is discarded

Payload
{
event: "config.revision.discarded";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.revision.rebased

Triggered when a draft revision is rebased onto the latest live state

Payload
{
event: "config.revision.rebased";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.revision.published

Triggered when a draft revision is published. Overlaps with config.updated but provides revision-specific context.

Payload
{
event: "config.revision.published";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
bulkPublishId?: string | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.revision.reverted

Triggered when a config is reverted to a previous published revision

Payload
{
event: "config.revision.reverted";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
revertedToVersion?: number | undefined;
bulkPublishId?: string | undefined;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.revision.reopened

Triggered when a discarded revision is reopened

Payload
{
event: "config.revision.reopened";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

config.revision.publishFailed

Triggered when a deferred publish (scheduled publish or auto-publish-on-approval) is given up on after failing — terminally, or after exhausting retries. The draft is left open for a human to resolve.

Payload
{
event: "config.revision.publishFailed";
object: "config";
api_version: string;
created: number;
data: {
object: {
id: string;
version?: number | undefined;
title?: string | undefined;
status: "draft" | "pending-review" | "approved" | "changes-requested" | "merged" | "discarded";
authorId: string;
authorEmail?: string | undefined;
contributors?: string[] | undefined;
revertedFrom?: string | undefined;
reviews: {
id: string;
userId: string;
decision: "approve" | "request-changes" | "comment";
comment?: string | undefined;
stale?: boolean | undefined;
dateCreated: string;
}[];
activityLog: {
id: string;
userId: string;
action: string;
dateCreated: string;
}[];
resolution?: {
action: "merged" | "discarded";
userId: string;
dateCreated: string;
} | undefined;
dateCreated: string;
dateUpdated: string;
baseConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedConfig: {
id: string;
/** Stable reference handle; used as `@config:key` in values */
key: string;
name: string;
/** The userId of the owner (or raw owner name/email for legacy records) */
owner?: string | undefined;
ownerEmail?: string | undefined;
/** The `key` of the config this one inherits from (lineage parent — the primary spine). Synthesized into `$extends` at resolution time and never stored in `value`. */
parent?: string | undefined;
/** Additional composition bases (config `key`s) layered on top of `parent`, in precedence order (later overrides earlier; all override `parent`; this config's own keys win last). Like `parent`, set via this field — never via a `@config:` entry in `value`. */
extends?: string[] | undefined;
/** This config's own base value as a JSON object (its declared fields only — inherited fields are layered in at resolution time, not stored here). Per-environment/project variants are expressed via `scopedOverrides`, not here. */
value?: Record<string, unknown> | undefined;
/** Ordered, first-match-wins environment/project-scoped variant selection. Each entry points at a flavor config (a child config, by `key`) whose value is deep-merged onto this config's resolved value when the (environment, project) scope matches — resolved at build time, per layer. This is how you create an environment-scoped override (as opposed to a plain child config): make a child config for the override value, then add it here with its scope. Send the complete list to replace it; an empty array clears all overrides. Entries must reference existing configs, may not reference this config itself, and may not be unreachable (fully subsumed by an earlier entry). */
scopedOverrides?: {
/** The `key` of the flavor config (a child config) whose value patches this config when the scope matches. */
config: string;
/** Environment ids this entry applies to. Empty/omitted = any environment. */
environments?: string[] | undefined;
/** Project ids this entry applies to. Empty/omitted = any project. */
projects?: string[] | undefined;
}[] | undefined;
/** Present ONLY when this config is an environment/project-scoped override (a "flavor") of another config. Its value is a patch that applies solely within the listed environments/projects, layered onto `parent` at resolution — it is NOT a standalone config. A plain config (including an ordinary child that just inherits from a `parent`) omits this field entirely. Read-only: create/change the relationship via the parent config's `scopedOverrides`, never by setting this directly. */
scopedConfig?: {
/** The base config this one is a scoped override of. */
parent: string;
/** Environments this override applies to (empty/absent = every environment). */
environments?: string[] | undefined;
/** Projects this override applies to (empty/absent = every project). */
projects?: string[] | undefined;
} | undefined;
description?: string | undefined;
/** The project this config belongs to (empty = all projects) */
project?: string | undefined;
archived?: boolean | undefined;
/** This config's own field definitions as a JSON Schema document (its contribution to the family's effective schema). Inherited fields are owned by ancestors and are not repeated here. */
schema?: {
type: "json-schema";
/** A JSON Schema document (an object). */
value: Record<string, unknown>;
} | undefined;
/** Whether this config family permits extra keys beyond the declared fields (child configs, feature rules, ad-hoc overrides). Only the root config's flag applies. Absent = inherit the org default. */
extensible?: boolean | undefined;
/** Cross-field validation rules (relational checks JSON Schema can't express, e.g. implications or comparing two fields), evaluated against the resolved value at publish. */
invariants?: {
/** Unique name for the rule. */
name: string;
/** A mongo condition (mongrule) boolean expression over the config's fields. */
rule: Record<string, unknown>;
/** Human-readable error shown when the rule is violated. */
message: string;
}[] | undefined;
/** Whether this config is locked: frozen at a published revision. While locked no change can be published past that revision until it is unlocked (which requires the `bypassApprovalChecks` permission). Drafts may still be created and edited. */
locked?: boolean | undefined;
/** Whether the experiment guard is enabled: publishing a change served to a running experiment soft-blocks (unless overridden with `ignoreWarnings: true` in the request body or `bypassApprovalChecks`). Turning it off requires `bypassApprovalChecks`. */
experimentGuard?: boolean | undefined;
/** The pinned published revision (present only when `locked`). Fetch it via `GET /configs-revisions/:key/:version` for a value guaranteed not to disappear or mutate — use it to pin reproducible builds. */
lockedRevision?: {
id: string;
version: number;
} | undefined;
/** Id of the user who locked the config (when `locked`). */
lockedBy?: string | undefined;
/** When the config was locked (when `locked`). */
dateLocked?: string | undefined;
dateCreated: string;
dateUpdated: string;
};
proposedChanges: {
op: string;
path: string;
}[];
bulkPublishId?: string | undefined;
failureReason: string;
terminal: boolean;
attempts: number;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}

user.login

Triggered when a user logs in

Payload
{
event: "user.login";
object: "user";
api_version: string;
created: number;
data: {
object: {
email: string;
id: string;
name: string;
ip: string;
userAgent: string;
os: string;
device: string;
};
};
user: {
type: "dashboard";
id: string;
email: string;
name: string;
} | {
type: "api_key";
apiKey: string;
id?: string | undefined;
name?: string | undefined;
email?: string | undefined;
} | {
type: "system";
subtype?: string | undefined;
id?: string | undefined;
} | null;
tags: string[];
/** The environments affected by the change described by this event. For live-state events (e.g. `feature.updated`) these are the environments whose effective configuration actually changed; for draft lifecycle events (`*.revision.*`) they are the environments the proposed changes would affect. Webhook environment filters match against this field. An empty array means the event has no environment-scoped impact (it will only be delivered to subscriptions without an environment filter). */
environments: string[];
containsSecrets: boolean;
}