Going to Production

Everything in this section, distilled into one gate. Walk it top to bottom before flipping traffic on. Each item links back to the page that explains the "why"; if any line surprises you, read that page first.

This is the section-wide checklist. For a webhook-receiver-only pass with copy-paste snippets, the webhook production checklist drills deeper into that one endpoint.

Credentials & authentication

  • API key is per-request. The SDK client is built from the incoming Authorization: Bearer <key> on every request. No SENT_DM_API_KEY env var, no boot-time singleton. — Authentication
  • No bearer token → 401. Requests without a valid key never reach the service layer.
  • The key is never logged or persisted. Not in logs, not in error messages, not in a database.

Webhook verification & replay protection

  • Every delivery is verified against the HMAC scheme on the raw body, before JSON parsing. — Signature verification
  • Constant-time compare against X-Webhook-Signature.
  • Replay window enforced: reject if |now − X-Webhook-Timestamp| > 300s.
  • Rotation tolerated: a space-separated signature header is accepted if any part matches a current secret.
  • The signing secret is never logged.

Resilience & idempotency

  • Acknowledge fast. The receiver returns 2xx within the endpoint's timeout, then processes. — Webhook receiver
  • Alerting on receiver failures. You'll know before 10 consecutive failures auto-disable the endpoint, not after. — Webhook receiver
  • Idempotent processing. Dedupe key derived from the payload (message_id + message_status), not from X-Webhook-ID (that's the config UUID). — Status tracking
  • Forward-only status. Out-of-order and duplicate events can't regress or double-apply status; terminal states lock.
  • Errors are mapped, not leaked — the service layer translates SDK failures into your own error contract. — Errors & resilience

Stateless deployment

  • Message-status store is shared (Redis/DB), not in process memory — so a send on one instance and its webhook on another agree. — Scaling & deployment
  • Webhook secrets are shared and readable by every instance that can receive a delivery.
  • Inbound dedupe store is shared, so the same webhook event isn't processed twice across instances. — Webhook receiver
  • Outbound idempotency-key store is shared, so a retried send doesn't double-send when the retry lands on a different instance. — Errors & resilience
  • Graceful shutdown on SIGTERM/SIGINT drains in-flight requests with a hard timeout cap.
  • Health probes wired: /live (pure process check) and /ready (checks shared deps) are distinct.

Security & network

  • HTTPS only. TLS terminated, http→https redirect, HSTS set; webhook URL registered as https://. — Security
  • No leftover local tunnel URL. If you developed against ngrok or similar, the registered endpoint now points at your real production URL, not a dev tunnel. — Endpoint management
  • Security headers on (helmet or equivalent).
  • CORS locked to your own front-end origins (no wildcard in prod) — and you're not relying on CORS to protect the receiver.
  • Input validated at the edge (zod/pydantic); malformed bodies 400, never reach the SDK.
  • Rate limits configured — a global limiter plus a stricter one on the receiver; backed by shared storage if you need a hard cap across instances.

Logging & monitoring

  • Structured (JSON) logs to stdout, with a request/correlation ID (X-Request-Id) on every line. — Observability
  • No secrets or PII in logs — no raw headers, no raw bodies, no recipient numbers.
  • Levels chosen by outcome (2xx→debug, 4xx→warn, 5xx→error).
  • Metrics emitted: sends, webhooks received/rejected, failures (counters); latencies (histograms); queue depth if applicable.
  • Alerts wired on error rate and webhook rejection rate; tracing hooks (OpenTelemetry) in place if used.

Final pre-launch

  • sandbox is OFF for production traffic — verify no live path sets sandbox: true, and test harnesses that do can't leak in. — Testing
  • Test suite green: service-layer units, route integration tests, and the webhook verifier's known-vector test.
  • Dashboard config confirmed in the Sent dashboard: correct endpoint URL, selected event types, endpoint enabled, a test delivery succeeds.
  • Rotation runbook exists (below) and the team knows where it is.

Key & secret rotation runbook

Two independent credentials rotate independently. Document both so a 3am rotation isn't improvised.

API key (compromised or scheduled)

  1. Issue a new key in the Sent dashboard.
  2. Roll it out to callers — because the key is a per-request credential, this is a client/caller change, not a redeploy of your integration.
  3. Confirm traffic on the new key (watch auth 401 rates), then revoke the old key.

Webhook signing secret (compromised or scheduled)

  1. Rotate via POST /api/webhooks/:id/rotate-secret — Sent returns the new secret once. — Endpoint management
  2. Persist the new secret to shared storage alongside the old one. During the overlap the receiver accepts either (Sent may send space-separated signatures).
  3. Once you've seen deliveries verify against the new secret, remove the old one.
  4. Never log either secret at any step.

Rotate without downtime by always allowing the old and new secret to overlap during the cutover. A "revoke then add" order drops legitimate deliveries in the gap; always "add new, drain, retire old."

All boxes checked? Enable the endpoint in the dashboard and watch closely for the first 24 hours — auth failures, webhook rejections, and send error rates are your early-warning signals.

Next steps

On this page