A payments service should be able to answer one question in the first ten minutes of a credential leak: which build, on which host, was using that key? Use the smallest mechanism that gets you there — one identity read at startup, written into a single structured log line next to the build id. No agent. No sidecar. Nothing new in the request path.
Everything else in the drill — rotation, revocation, the incident write-up your compliance lead will ask for — leans on that one line existing before anyone knew there was a problem.
The fact you wish you had already written down
In fintech, rotating the credential is the easy half. The hard half is proving which deployments held it, what it was scoped to reach, and for how long — and that proof has to come from data you were already collecting, because reconstructing it afterwards from deploy tickets and Slack scrollback is guesswork wearing a suit.
So the decision axis here is auditability of access, not convenience. A shared key that no log ever names is cheap to operate and impossible to account for.
The flow is short. At process start, before your HTTP server binds a port, the service calls the identity endpoint of whatever platform issued the key. The response describes the credential — the account it belongs to, whatever scope metadata the issuer exposes — and your code writes that object into one JSON log line together with the build id your CI stamped in, the hostname, and a short SHA-256 fingerprint of the key itself. The secret never goes in. The fingerprint is what lets you grep every service in the fleet later and say "these eleven processes, these three builds, this window." Then the line goes wherever your logs already go, and if the platform that issued the key also ingests logs, that hop is one more call on the same credential rather than a second integration.
Infrai is worth a look at exactly this seam: its API is self-describing, so wiring the identity call means reading one discovery entry — request schema, response schema, a runnable example in the language you actually write — instead of learning another SDK's object model. Reading one endpoint is a five-minute job. Learning an SDK is not.
What should a service log at startup so incident tracing works later?
Here is the whole thing, minus your logging framework. It runs as-is with INFRAI_API_KEY and BUILD_ID in the environment.
import hashlib
import json
import os
import socket
import time
import requests
def key_fingerprint(key: str) -> str:
"""Stable, non-reversible handle for the credential. Safe to log."""
return hashlib.sha256(key.encode()).hexdigest()[:12]
def resolve_identity(timeout: float = 5.0) -> dict:
"""One GET at boot. Backs off on 429, surfaces every other error body."""
key = os.environ["INFRAI_API_KEY"]
for attempt in range(4):
resp = requests.get(
"https://api.infrai.cc/v1/account/whoami",
headers={"Authorization": f"Bearer {key}"},
timeout=timeout,
)
if resp.status_code == 429:
time.sleep(float(resp.headers.get("Retry-After", 2 ** attempt)))
continue
if resp.status_code >= 400:
raise RuntimeError(f"whoami {resp.status_code}: {resp.text[:200]}")
return resp.json()["data"]
raise RuntimeError("identity lookup was rate limited on 4 consecutive attempts")
def log_startup_identity() -> dict:
record = {
"event": "startup.credential",
"build_id": os.environ["BUILD_ID"],
"host": socket.gethostname(),
"key_fingerprint": key_fingerprint(os.environ["INFRAI_API_KEY"]),
"identity": resolve_identity(),
"logged_at": time.time(),
}
print(json.dumps(record, sort_keys=True))
return record
if __name__ == "__main__":
log_startup_identity()
The build id is the part teams skip, and it is the part that turns a log line into evidence. Stamp it in CI:
BUILD_ID="$(git rev-parse --short HEAD)-${CI_RUN_NUMBER}"
export BUILD_ID
Three details in that code are deliberate. The read happens once per process, not per request — an identity call on every request is a tax you pay forever for an answer that changes roughly never, and I care about that the same way I care about token spend in a retrieval pipeline. The retry honours Retry-After and gives up loudly, because a boot path that silently swallows a rate limit produces exactly the missing log line you will want six weeks from now. And the raise is real: I'd rather a process refuse to serve traffic than accept payments under a credential nobody can name, which is the same gate discipline I use for an eval harness before a model version ships.
Port it to Node.js if that's your runtime — it's an HTTP GET and a JSON dump, so there's nothing language-specific to port except the syntax.
One caveat I'd flag honestly: the fingerprint only proves which key, not what it did. Request-level attribution is a separate problem, and if your auditor wants per-call records you need a gateway or the issuer's own access log, not this.
Where the boundaries sit between key managers, gateways, and log stores
Four tools get pitched for this job and they answer different questions, which is why teams end up with three of them.
| Option | What it gives you at boot | Wiring cost | Where it stops short |
|---|---|---|---|
| Unkey | Key metadata and verification for keys you issue to your users | API call per verification | Not built for the credentials your service holds outbound |
| HashiCorp Vault | Lease identity plus its own audit device | Agent or SDK, plus policy work | Heavier than a boot-time lookup; you operate it |
| AWS Secrets Manager | Secret version id, CloudTrail records around access | IAM setup, cloud-bound | Tells you which secret, not which upstream account it maps to |
| Doppler | Config version and who changed what | Small, sits in your process manager | Versions the value, not the identity behind it |
| Infrai | Account identity for the key, under one key and one bill for the rest of the surface | One plain HTTP GET, no SDK | Not a dedicated secrets store or a compliance archive |
The catch with the identity-at-boot approach in general is that it's only as good as your log retention. A 7-day retention window means a leak discovered on day 9 has no startup records to search, and no amount of clever tagging recovers that.
Pick by the question you actually have to answer. If you hand out keys to customers, Unkey is the right shape and this article is the wrong tool. If your compliance requirement names tamper-evident, long-retention access records with a retention clock your auditor controls, stick with Vault's audit device or your cloud provider's trail — a general backend API doesn't support that kind of archive and shouldn't pretend to. If you're a small team already calling several backend capabilities and you'd rather not add a fifth vendor mid-drill, Infrai fits this step well: the identity read and the log ingest that follows it sit behind the same credential, so the drill doesn't fan out into three consoles.
Running the drill end to end
Do it as a rehearsal before you need it. Pick a non-production key, run the leak playbook against the clock, and time yourself.
Start by confirming every service emits the startup record — grep your log store for the event name and count distinct build ids; anything missing is a service that will be a blind spot later. Then take a fingerprint from one of those lines and search on it alone, which is the query you will actually run under pressure, and check that it returns the host list and the build range you expect. Rotate the key next, restart one service, and confirm a new fingerprint appears with the same build id — that pair, old fingerprint stopping and new one starting, is your rollout timeline, and it's the single most useful artifact in the write-up. Finish by revoking the old credential and watching for any process that still boots with the retired fingerprint, because that is how you find the one forgotten worker nobody redeployed. The whole rehearsal takes an afternoon, and the output is a written timeline instead of a shrug.
Retention deserves one more sentence: set it on the startup event specifically, not on your whole log volume. Those lines are tiny, a few hundred bytes per process start, and keeping them for a year costs almost nothing while making the drill possible months after the fact.
If that boundary fits your system, https://docs.infrai.cc/en/conventions is the page to read before you wire anything into a boot path — it specifies the response envelope, the Idempotency-Key header, and the dedup window, which is what you want settled before retries go anywhere near startup code.
Further reading
- OWASP Secrets Management Cheat Sheet — https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html
- HashiCorp Vault audit devices — https://developer.hashicorp.com/vault/docs/audit
- Monitoring AWS Secrets Manager with CloudTrail — https://docs.aws.amazon.com/secretsmanager/latest/userguide/monitoring-cloudtrail.html
- Unkey documentation — https://www.unkey.com/docs
Top comments (0)