Rotating and safely storing secrets
For operators who want to periodically update (rotate) the secrets of an app running in production.
A secret has the property that "the longer you keep using the same one, the more leak risk accumulates". A departed member viewed it in the past, it slipped into a log, it was carried out in a backup — all of these are plausible paths. The basic strategy is to bound the blast radius of a leak by time through periodic rotation.
Guideline rotation frequency
| Target | Recommended frequency | Triggers for immediate update |
|---|---|---|
| Client secret | Once a year | Suspected leak, offboarding, a leak incident |
| Webhook secret | Once a year | Same as above |
| Access token | Automatic (1 hour) | — |
| Refresh token | Automatic (90 days) | — |
Client secret rotation procedure
ReceiptRoller supports a "parallel period for the old and new secrets", so you can rotate with zero downtime.
- Developer portal → App → Credentials → "Issue an additional secret"
- Store the new secret securely
- Update the value in your secret management service (AWS Secrets Manager, etc.) to the new secret
- Deploy the production environment in sequence (or reflect the environment variable via a rolling restart)
- Confirm via logs that all environments are running on the new secret
- "Revoke" the old secret in the developer portal
During the parallel period, both secrets are valid. Even if a request arrives with the old secret in the middle of a deploy, authentication passes, so the switch can be done with zero downtime.
Webhook secret rotation
The Webhook secret can also be operated with a parallel period. Implementing the receiving side to "try signature verification with both the old and new secrets" makes the switchover window safe.
function verify(body, signature, ts) {
return verifyWith(body, signature, ts, NEW_SECRET)
|| verifyWith(body, signature, ts, OLD_SECRET);
}
After the switch is complete, remove the reference to OLD_SECRET from the code.
Storage best practices
- Use a secret management service: AWS Secrets Manager / Azure Key Vault / GCP Secret Manager / HashiCorp Vault
- Encrypt environment variables: use the encryption feature when registering them in CI (GitHub Secrets, GitLab CI Variables, etc.)
- Access auditing: keep it in a state where you can trace who retrieved a secret and when
- Least privilege: only production service accounts can retrieve production secrets
- Exclude from backups: do not include secrets in DB backups or log archives
- Offboarding: immediately rotate secrets a departing member had access to
Things you must not do
| Anti-pattern | What to do |
|---|---|
| Committing a .env file to git | Add it to .gitignore; delete any past inclusion per-history + regenerate the secret |
| Sharing secrets over Slack or email | Share via a secret management service |
| Embedding in client-side code | Move it to the server side; regenerate immediately |
| Outputting secrets to error logs | Masking, delete logs, regenerate |
| Using the same secret for years | Put an annual rotation on the calendar |