The very first `bin/kamal deploy` needs less than you'd expect: a Rails master key and a container
registry token. Everything else, Stripe, Resend, S3, is genuinely optional at this stage, since the
app runs on its Local fakes until you configure real credentials. See
[Bring Your Own Keys](/docs/bring-your-own-keys/optional-integrations) for the full reference.

## What secrets actually need to exist

Kamal's secrets file, `.kamal/secrets`, reads each value from a gitignored local file, falling back
to an environment variable, which is how CI injects them. For a local `bin/kamal deploy`, the files
worth creating are:

| File | Value |
| --- | --- |
| `config/master.key` | Your Rails master key |
| `.kamal/.registry-token` | A container registry token with write access, for pushing the built image |
| `.kamal/.stripe-secret-key` | Stripe live secret key, optional |
| `.kamal/.stripe-webhook-secret` | Stripe webhook signing secret, optional |
| `.kamal/.resend-api-key` | Resend API key, optional |

The very first deploy needs only the master key and the registry token. Set a real price id in
`config/deploy.yml` before you turn on billing for real; see
[Stripe Setup](/docs/billing-entitlements/stripe-setup).

## Required before production will even boot

Rails will not boot in production without a secret key base; it signs sessions and cookies, and
there's no safe default the way development and test have. This is the most common first-deploy
failure precisely because it doesn't show up locally: dev and test each generate a temporary key
automatically, so a fresh clone runs fine right up until the first real deploy fails. See
[Secret Key Base](/docs/bring-your-own-keys/secret-key-base) for how to generate one.

## The Stripe webhook events, if you're turning on billing

If you enable real Stripe billing, your webhook endpoint needs specific events enabled, not just
"all events." A handler exists for each of these, and an event that isn't enabled simply never
fires, with no error anywhere to notice: entitlement quietly stops tracking reality.

| Event | What breaks without it |
| --- | --- |
| `checkout.session.completed` | Purchases don't grant access (the return-URL redirect covers some of this, but only when the buyer actually lands there) |
| `invoice.payment_succeeded` | Renewals don't extend access |
| `invoice.payment_failed` | Failed renewals don't enter a past-due grace window |
| `customer.subscription.updated` | Plan changes and cancellations don't sync; the UI keeps saying "renews" |
| `customer.subscription.deleted` | Access isn't revoked when a subscription actually ends |
| `charge.refunded` | A refunded buyer keeps access |
| `charge.dispute.created` | A buyer who charges back keeps access |
| `radar.early_fraud_warning.created` | A charge the card issuer already flagged as fraudulent turns into a full dispute you could have avoided |

An early fraud warning is the issuer flagging a charge as fraudulent before the cardholder actually
disputes it. Revoking access and refunding immediately in that window usually means no formal
dispute ever gets filed, which avoids both a dispute fee and a mark against the dispute ratio card
networks judge your account on. This is also why the Stripe webhook endpoint is exempt from the
app's general per-IP rate limit: Stripe delivers from a small pool of IPs and can auto-disable an
endpoint that keeps returning non-2xx responses, and a throttled webhook fails both silently and
completely.

A daily job re-checks lapsed subscriptions against Stripe, so a single missed webhook delivery
self-heals on its own. It can't cover refunds, disputes, or fraud warnings, though; none of those
have a lapse date for the job to notice, which is exactly why those specific events need their own
handlers rather than relying on the daily sweep.

## Verify

```bash
bin/kamal setup
bin/kamal logs -f
```

Then confirm the app answers, both on the server directly and through your public domain, before
you consider the deploy done.

## Next

Get your domain pointed at the server: [Edge and DNS Setup](/docs/deployment/edge-and-dns-setup).
