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. NoSENT_DM_API_KEYenv var, no boot-time singleton. See 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. See Signature verification.
- Constant-time compare against
X-Webhook-Signature. - Replay window enforced: reject if
|now − X-Webhook-Timestamp| > 300s. - Rotation ready: the verifier loads candidate secrets per endpoint at delivery time, so a rotated secret takes effect without a code deploy. Sent signs each delivery with exactly one signature, using the current secret only.
- The signing secret is never logged.
Resilience & idempotency
- Acknowledge fast. The receiver returns
2xxwithin the endpoint's timeout, then processes. See Webhook receiver. - Alerting on receiver failures. You'll know before 10 consecutive failures automatically turn off the endpoint, not after. See Webhook receiver.
- Idempotent processing. Dedupe key derived from the payload (
message_id+message_status), not fromX-Webhook-ID(that's the config UUID). See 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. See 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. See 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. See Webhook receiver.
- Outbound idempotency-key store is shared, so a retried send doesn't double-send when the retry lands on a different instance. See Errors & resilience.
- Graceful shutdown on
SIGTERM/SIGINTdrains 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→httpsredirect, HSTS set; webhook URL registered ashttps://. See 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. See 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. See Observability. - No secrets or PII in logs: redact raw headers, raw bodies, and 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
-
sandboxis OFF for production traffic. Verify no live path setssandbox: true, and test harnesses that do can't leak in. See 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 (the Key & secret rotation runbook section on this page) and the team knows where it is.
Key & secret rotation runbook
Two independent credentials rotate independently. Document both so a 3 AM rotation isn't improvised.
API key (compromised or scheduled)
- Issue a new key in the Sent dashboard.
- 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.
- Confirm traffic on the new key (watch auth
401rates), then revoke the old key.
Webhook signing secret (compromised or scheduled)
- Rotate via
POST /api/webhooks/:id/rotate-secret. Sent returns the new secret once, and the old secret is invalid immediately; every subsequent delivery is signed only with the new secret. See Endpoint management. - Persist the new secret to shared storage in the same operation, so every instance can verify the very next delivery. Deliveries that land before the new secret is live fail verification; Sent retries them, but 10 consecutive failures auto-disable the endpoint.
- Retire the old secret from the verifier's candidate set once deliveries verify against the new one. Leaving it briefly is harmless: it never matches again.
- Never log either secret at any step.
There is no dual-signing overlap. Sent signs with exactly one secret at a time, and rotation swaps it the moment the call returns, so "rotate, then persist at once" is the only safe order. Every delivery in the gap between those two steps is rejected and retried; keep the gap to seconds, not a deploy cycle.
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.