Undo, for production configuration
Publish snapshots every variable as ciphertext in one transaction. Every rollback leaves an attributed history entry.
The guarantees, precisely
Publishing copies every live variable into a numbered snapshot as ciphertext, so it never needs the workspace key. Rolling back replays a snapshot in one transaction.
Full definition
Publishing an environment copies seven fields for every live variable into a numbered snapshot: name, value, type, secret flag, visibility, required flag and default value. The value is copied verbatim as ciphertext, which is why publishing never needs the workspace key and the server never learns anything by doing it. Labels are derived server-side from a count, so the first publish is v1, and each publish only lands if the environment is still at the revision it read, so two concurrent publishes cannot race to the same number. Rolling back replays a snapshot in one more transaction and tells you exactly how many variables it added, updated and removed.
Versions
Publishing, listing, inspecting and restoring, with every flag.
Variable history
Reading a single variable's change history and what each event carries.
Webhooks overview
Delivery semantics, retries, and the guarantees you can rely on.
Verifying signatures
Standard Webhooks signing, the tolerance window, and the raw-body rule.
Version published event
The exact payload shape emitted when someone publishes an environment.
Rotating with version history
Why version entries must be re-encrypted in the same run as live variables.
Per publish and per rollback
1 transactionBoth run as one atomic database batch that first checks the environment has not changed since it was read, so a publish cannot interleave with a rollback and neither can half-apply.
Per-variable history
4 event typescreated, updated, deleted and restored. Each event denormalizes the actor name and email next to the user id, and the foreign key nulls on delete, so history keeps naming the person after their account is gone.
Versions per environment
1000A hard cap. The publish that would be the 1001st is refused rather than silently dropping the oldest, so you find out at publish time instead of when you need the snapshot.
Previous-value retention limit
8192 charsA single-variable update stores the previous ciphertext in its event when it fits under that length, which is what powers "copy old value" in the history drawer. Bulk applies and rollbacks record no previous value.
Gating versions
3 permissionsversions:read, versions:create and versions:rollback are checked server-side on every version controller. The built-in Viewer role carries only the read one. The plan gates on top: Free keeps the latest published version only, and history plus rollback need Dev or above.
Route, command or SDK method
no deleteThere is no path anywhere that deletes a version row, so labels are never recycled while the environment exists and a published label stays a stable address.
Versioning and history FAQ
No, only to a version someone explicitly published. Changes made between publishes are not recoverable as a set. Individually they often are, because a single-variable update stores its previous ciphertext in the event, but a bulk apply or a rollback records no previous value. Publish before anything you might want to undo, and treat publishing as cheap.
Publishing works on every plan and no version is ever deleted, but Free keeps only the latest published version in view: listing returns the latest snapshot and reports how many are hidden, reading an earlier version answers with a plan.upgrade_required error, rollback is refused with the same error, and per-variable history returns the latest entry. Dev, Team and Enterprise get the full history and rollback, and everything published while on Free becomes visible the moment you upgrade.
No, and this catches people out. Rollback replays the target version and writes no new version, so if you roll back and then want to get to where you just were, there has to have been a publish. Publish first, then roll back. The SDK documentation says the same thing at the call site for exactly this reason.
Not yet, and we would rather be precise than imply one. Every workspace action is recorded with the actor, the event type and a full payload, and each one is delivered to your webhook endpoint within seconds. What does not exist today is an endpoint or dashboard page that queries those rows back. The two surfaces you can query now are per-variable history and the version list; for a searchable workspace-wide record, point a webhook at a sink you control.
No. Nothing logs reads. The recorded events are created, updated, deleted and restored, which is to say writes. The nearest thing to read telemetry is a lastUsedAt timestamp on each API key, throttled to at most one write a minute, which tells you a key is in use but not what it fetched.
It is append-only in the sense that no route, command or SDK method exposes an update or delete for a version, and that is the honest way to put it. There is no hash chain, no signature over the rows and no write-once storage, and version ciphertext is deliberately rewritten in place when you rotate the workspace passphrase. Treat a version as a frozen set of variables rather than frozen bytes.
No, they are per environment, derived from that environment's version count. v13 in staging and v13 in production are unrelated snapshots. For anything that has to be globally unambiguous, such as pinning a build, use the version uniqueId rather than the label.
No, and this is deliberate. Duplicate copies products, projects, environments and variables including their ciphertext, but nothing in the duplication path touches versions. A duplicated environment starts with a clean history, which is usually what you want when the copy is a new staging branch rather than a clone of the past.
It starts fresh. A rollback soft-deletes variables missing from the snapshot and inserts new rows for names that are absent, and history is keyed to the variable row, so a restored variable begins a new history while the old events stay attached to the retired row. The version snapshots remain the continuous record across that boundary.
