Skip to content

Rotating a credential without restarting the workload

This is the workflow Excalibur is most often hired for. The workload never sees the old key, never sees the new key, and never restarts.

The setup

The workload makes continuous requests using a placeholder:

while :; do
  curl -sS -o /dev/null -w "%{http_code}\n" --max-time 4 \
    https://api.stripe.com/v1/charges \
    -H "Authorization: Bearer XCALIBUR_STRIPE_SECRET_KEY" \
    -d amount=$RANDOM -d currency=eur
done

The proxy is substituting the current real value into every outbound request. So far, so normal — 200 200 200 ….

Rotate

One operator command. Same placeholder name, new real value:

curl -X POST http://localhost:8443/api/placeholders \
  -H "Content-Type: application/json" \
  -d '{
        "placeholder":  "XCALIBUR_STRIPE_SECRET_KEY",
        "real_value":   "sk_live_NEW_VALUE_FROM_VAULT",
        "provider_id":  "stripe",
        "route_family": "stripe"
      }'

Or via CLI:

excalibur-ctl placeholder add \
  --name  XCALIBUR_STRIPE_SECRET_KEY \
  --value sk_live_NEW_VALUE_FROM_VAULT \
  --provider stripe

The POST /api/placeholders call is upsert — same name, new value. The escrow record is replaced atomically.

What the workload sees

Nothing. The output keeps printing 200 200 200 ….

In the demo capture (scene 09 of the recorded walk-through), the workload runs ~80 charges across the cutover. Every single one returns 200. There are no 4xx, no 5xx, no retries.

in-flight responses across cutover:
  HTTP 200  count 79

Why there are no failures

  • Requests already in flight at the moment of the write complete against whichever value was active when the proxy looked the placeholder up. They are not rolled back, not re-signed, not cancelled.
  • Requests that arrive after the write resolve to the new value on lookup. There is no warm-up period.
  • The workload's HTTP client never retries because it never sees a failure. There is no race window for the workload to observe.

The only place the cutover is visible is the proxy itself — the new value appears in escrow, and the next outbound substitution uses it.

Where you see it in the dashboard

Open Escrow & Surrogates. The Placeholder vault table now shows a new "last rotated" timestamp on the row.

Placeholder vault & lifecycle

The Credential lifecycle panel at the bottom of the same page streams a placeholder_rotated event in near-real-time.

What gets recorded

Event Details
placeholder_updated Both the upsert path and the rotation path emit this
placeholder_rotated Specifically signals a value change (not a metadata-only update)

Both events show up on Audit as you would expect; filter by type=placeholder_* to scope to credential lifecycle.

Common variations

Rotation integrates with vaults, webhooks, and translation-rule changes.

Scheduled rotation from a vault

Wire your existing vault rotation hook to call the same POST /api/placeholders endpoint. Excalibur does not need to participate in your secret-generation workflow — it is the read side of the rotation, the workload-facing surface.

Webhook-driven rotation

Excalibur can also push notifications outbound on rotation. See the webhook surface (POST /api/webhooks with event_types=["credential_expired"] or ["credential_rotated"]) so downstream automation (audit log, SIEM, ticketing) is informed every time a key turns over.

Rotation with translation-rule changes

If the rotated value must also have a different scope (e.g. a key that was full-access becomes read-only), update the relevant translation rule in the same operator window. The next request will be subject to the new rule immediately.

What this workflow does NOT require

  • No workload restart, no rolling deployment.
  • No coordination window with the workload team.
  • No refresh API the workload has to call.
  • No client SDK retries.
  • No client-side awareness that anything happened.

That is the point.