# Production checklist

> Everything to settle before real customers receive webhooks, with a link to the details for each item.

Source: https://good-webhooks.dev/docs/operations/production



Work through this list once before launch. Each item links to the page that explains it.

## Keys and secrets [#keys-and-secrets]

* [ ] The storage encryption key is generated once, kept in a secret manager, and loaded by every app and worker process. Never generate it at startup. [Manage endpoints](https://good-webhooks.dev/docs/guides/endpoints.md)
* [ ] Signing secrets are shown to the customer once, over a secure channel, and never logged. [Security model](https://good-webhooks.dev/docs/concepts/security.md)
* [ ] You know how to rotate both a signing secret and the storage key. [Key rotation](https://good-webhooks.dev/docs/operations/key-rotation.md)

## Database [#database]

* [ ] Migrations run through your migration tool, not at app startup. [Migrations](https://good-webhooks.dev/docs/operations/migrations.md)
* [ ] Every process calls `check()` at startup and fails fast on `SCHEMA_MISMATCH`.
* [ ] Pool sizes and statement timeouts are budgeted across app, worker, and cleanup processes. [Run workers](https://good-webhooks.dev/docs/operations/workers.md#bound-database-waits)

## Publishing [#publishing]

* [ ] Events are published inside the transaction that changes your data, where the tables share a database. [Publish events](https://good-webhooks.dev/docs/guides/publish.md#publish-inside-your-transaction)
* [ ] Every `publish()` that can repeat has an `idempotencyKey` derived from the business fact.
* [ ] Your app authorizes the caller before choosing a scope. Scopes don't check permissions. [Scopes](https://good-webhooks.dev/docs/concepts/scopes.md)
* [ ] `allowLocalhost` is off.

## Worker [#worker]

* [ ] At least one worker runs as its own supervised process, restarted with backoff on failure. [Run workers](https://good-webhooks.dev/docs/operations/workers.md)
* [ ] `SIGTERM` aborts the worker's signal, and the supervisor's kill deadline is longer than `shutdownGraceMs`.
* [ ] Worker failures are logged and alerted on: either `run()` rejects and the process exits, or your `onError` logs and you watch that deliveries keep moving. [Handle failures](https://good-webhooks.dev/docs/operations/workers.md#handle-failures)
* [ ] You have read what a crash, a dropped connection, and a hard kill do to in-flight work. [Failure behavior](https://good-webhooks.dev/docs/operations/failure-behavior.md)
* [ ] You have compared your receiver latency and volume against the measured profile and sized `concurrency` and `maxInFlight` accordingly. [Performance](https://good-webhooks.dev/docs/operations/performance.md)

## History [#history]

* [ ] A cleanup job calls `prune()` on a schedule. Workers never delete history. [Retention](https://good-webhooks.dev/docs/operations/retention.md)
* [ ] Delivery history is treated as customer data: it holds payloads and response bodies.
* [ ] Support staff know how to inspect deliveries and replay one. [Retries and replay](https://good-webhooks.dev/docs/operations/retries-and-replay.md)

## Receivers [#receivers]

* [ ] Receivers verify the raw body and record `event.id` with their change, so retries and replays are harmless. [Receive webhooks](https://good-webhooks.dev/docs/guides/receive.md)
* [ ] Event names stay backward compatible. Breaking payload changes get a new name, such as `invoice.paid.v2`.

## Upgrades [#upgrades]

* [ ] The package version is pinned exactly, and you read the release notes before upgrading. [Compatibility](https://good-webhooks.dev/docs/operations/compatibility.md)
