All event types use the format entity.action. There are 48 of them. Each webhook endpoint subscribes to a subset; only matching events are delivered.
GET /v1/webhooks/event-types returns the live list plus a totalCount, so you never have to hardcode this page.
Delivery is scoped to the workspace the event belongs to. On top of that, an event that resolves to a private product is only delivered to endpoints whose creator can access that product.
Workspace
| Type | Fires when |
|---|---|
workspace.created | A new workspace is created |
workspace.updated | Name, slug, icon or the Virtual Envs flag changes |
workspace.deleted | The workspace is soft-deleted |
An endpoint can only be created inside a workspace that already exists, so nothing is subscribed at the moment workspace.created fires. The event is recorded, but it has no endpoint to reach in its own workspace.
Product
| Type | Fires when |
|---|---|
product.created | A new product is created, including by Duplicate |
product.updated | Name, slug, icon or isPrivate changes |
product.deleted | The product is deleted |
Product access
| Type | Fires when |
|---|---|
product_access.granted | A member is granted access to a product (payload carries product with id, name and slug, plus member.userId) |
product_access.revoked | A member's access to a product is revoked (same payload shape) |
Both fire for every grant and revoke, not only on private products. Marking a product private is what makes the grant list load-bearing; the events are emitted either way. A grant that changes nothing, because the member already had access, emits nothing.
Project
| Type | Fires when |
|---|---|
project.created | A new project is created, including by Duplicate |
project.updated | Name, slug or icon changes |
project.deleted | The project is deleted |
Environment
| Type | Fires when |
|---|---|
environment.created | A new environment is created in a project, including by Duplicate |
environment.updated | Name or slug changes |
environment.deleted | The environment is deleted |
environment.exposed | The environment is turned into a public encrypted feed |
environment.unexposed | Exposure is turned off, one event per environment when a workspace disables Virtual Envs |
environment.example_enabled | The environment starts publishing a blank-value .env.example at a public URL |
environment.example_disabled | The example link is turned off |
Variable
| Type | Fires when |
|---|---|
variable.created | A new variable is created |
variable.updated | Value, name, type, isSecret, visibility, required or defaultValue changes |
variable.deleted | The variable is deleted |
variable.bulk_created | Variables created via the bulk endpoint (payload includes environmentId, count and a variables array of id and name) |
variable.bulk_deleted | Variables deleted via the bulk endpoint (same payload shape) |
variable.bulk_updated | One metadata patch is applied to many variables at once (payload adds fields and changes, and never carries names or values) |
variable.applied | An apply call writes creates, updates and deletes to an environment in one request (payload includes added, updated and removed counts) |
Variable payloads never include the encrypted value or the plaintext. They carry ids and names, plus type, isSecret and environmentId on the single-variable events, and counts on the batch events.
Version
| Type | Fires when |
|---|---|
variable_version.created | A version snapshot is published |
variable_version.rolled_back | An environment is rolled back to an earlier version (payload includes versionLabel and stats with added, updated and removed counts) |
Member
| Type | Fires when |
|---|---|
member.invited | A new email invite is sent |
member.invite_resent | The invite email is resent |
member.invite_revoked | A pending invite is revoked |
member.accepted | The invitee accepts and joins the workspace |
member.updated | The member's role changes |
member.removed | The member is removed, or leaves on their own (only the self-leave payload carries left: true) |
API key
| Type | Fires when |
|---|---|
key.created | A new API key is created |
key.updated | Name, scopes, projectIds, or active state changes |
key.rotated | The key value is rotated |
key.deleted | The key is deleted |
CLI token
| Type | Fires when |
|---|---|
cli_token.created | A device-code flow completes and a CLI token is issued |
cli_token.revoked | A CLI token is revoked (dashboard or envless logout) |
Role
| Type | Fires when |
|---|---|
role.created | A custom role is created |
role.updated | Name, description, or permissions change |
role.deleted | A custom role is deleted |
Subscription
| Type | Fires when |
|---|---|
subscription.created | A workspace's first subscription is created (Polar) |
subscription.updated | Status, billing period, cancellation or revocation is recorded |
subscription.duplicate_detected | Polar sent a second subscription for a workspace that already has a live one. Nothing is written; the payload carries duplicate and existing, each with polarSubscriptionId, polarProductId, status, currentPeriodStart and currentPeriodEnd, so you can refund and cancel the duplicate in Polar |
Subscription events have no actor because they originate from Polar webhooks, not a dashboard user. The delivered body still carries the actor object, with userId, name and email all null. webhook.delivery_failed is the same.
Webhook
| Type | Fires when |
|---|---|
webhook.created | A new webhook endpoint is configured |
webhook.updated | Name, URL, description, event subscriptions or enabled state changes |
webhook.deleted | A webhook endpoint is removed |
webhook.secret_rotated | The signing secret is rotated (the new secret is never in the payload, only secretLastFour and secretRolledAt) |
webhook.delivery_failed | A delivery exhausts all 5 attempts and gives up |
To prevent infinite loops, webhook.delivery_failed is not emitted for a failed webhook.delivery_failed delivery.