Security
How Emithook keeps inbound events authentic, outbound deliveries safe, and payloads private.
Inbound authenticity
Every inbound endpoint's signature is verified using the provider preset: Shopify, Stripe (300 s tolerance), Slack (v0=, 5-min replay window), Meta, Razorpay, or generic HMAC. Per the founding invariant (ADR-0021), the immortal edge accepts and durably buffers every request in <100 ms and never drops an accepted event — it does not verify or 401 on the accept path. Verification runs in the processing plane as a verdict: a verified event is routed and delivered; a bad signature is quarantined — durable and inspectable, but never delivered; an unknown endpoint is dropped (with a metric); an inactive/paused endpoint is parked (durable, replayable). Unauthenticated events are therefore still never delivered — the guarantee is unchanged; only where verification happens moved (off the accept path, so a flood of bad signatures can never threaten ingest availability). Optional per-endpoint IP allowlists and edge WAF/rate-limiting add defense in depth.
For inbound email, the analogue is SPF / DKIM / DMARC verification, recorded on each event with a configurable drop/quarantine policy.
Outbound signing
Outbound webhooks are signed with the Standard Webhooks spec, verbatim:
- Headers
webhook-id,webhook-timestamp(Unix seconds),webhook-signature. - Signed content is exactly
{id}.{timestamp}.{body}(the raw bytes sent), HMAC-SHA256 → base64 →v1,<sig>. - Per-destination secret
whsec_…. Rotation signs with current + previous keys (space-delimited) for a 24 h overlap, so in-flight messages keep verifying.
Receivers verify with any off-the-shelf library — against the raw request body (the #1 cause of verification failures).
SSRF protection — the highest-severity outbound control
Because customers (and, in the Send platform, their end-customers) supply arbitrary destination URLs, the delivery path is a Server-Side Request Forgery surface — the class behind the Capital One breach via the cloud metadata endpoint. Emithook applies these controls at delivery time, not just at registration:
- HTTPS and standard ports only; reject encoded-IP literals and
@userinfo. - Resolve DNS through a public resolver, validate the resolved IP against the reserved/private denylist (
169.254.169.254,127.0.0.0/8,10/8,172.16/12,192.168/16,169.254/16,fc00::/7), then connect to that pinned IP — defeating DNS-rebinding (TOCTOU). - Never follow redirects (
3xx= failure). - Strip internal headers/credentials.
- Route all delivery egress through a Smokescreen-style proxy in an isolated subnet with a default-deny network policy that cannot reach internal services.
Defense in depth = an app-layer IP check and network-layer egress isolation. This is a launch gate, not a hardening nice-to-have.
Data residency & retention
Payloads at rest stay in your selected region — the managed cloud is multi-region by design (ADR-0020), with India (ap-south-1) available today and more regions coming — excepting transient edge buffering during a regional failover (documented in the DPA). Encrypted at rest (SSE/KMS). Retention is a per-domain control (7/30/90 d, or "metadata only" to null payloads for sensitive endpoints); the hourly archive has its own longer lifecycle (default 13 months) then hard delete.
Access control
Scoped API keys (read ⊂ write ⊂ admin) gate every API/CLI/MCP call. Per-org RBAC (Admin/Developer/Viewer), optional 2FA/MFA and SSO/SAML, an active-sessions view with per-session revoke, and an audit log of console actions. Least-privilege IAM per service; secrets in a secrets manager, never inline.
Next
- API conventions — scopes, idempotency, errors.
- Self-hosting — running it in your own environment.