Pushrail Docs
Open app
Guides

Private Payload Mode

Seal event contents with a key the dashboard and APIs cannot decrypt, so Pushrail's own product surface can never display them.

Private Payload Mode

Pushrail already encrypts data at rest at the database layer. Private Payload Mode is a step further: event contents are sealed with a key the dashboard and APIs cannot decrypt, so Pushrail's own product surface can never display them. Only delivery workers can open the seal, and only at the instant of sending or replaying.

This is an opt-in, per-environment "operational privacy" feature. It exists for regulated tenants (healthcare, financial) who need to limit who can see the contents of the events they push through Pushrail, including Pushrail itself.

What it does

When Private Payload Mode is on for an environment, every event ingested afterward has its payload and metadata sealed before it is stored. Each delivery attempt's request and response bodies are sealed the same way. A sealed record carries ciphertext only; the plaintext columns are null. The dashboard, the APIs, and exports all show [sealed] in place of the contents.

Delivery still works end to end. The delivery worker holds the one key that can open a seal, decrypts the event in memory at send time, runs your transforms, delivers to the destination, then discards the plaintext. Nothing plaintext is written back.

Threat model — be precise

Private Payload Mode protects event contents at rest and on the product surface. It honestly guarantees:

  • The dashboard and all tenant APIs cannot decrypt sealed payloads (the process has no private key). [sealed] is shown to everyone.
  • Sealed contents at rest (Postgres rows, Redis batch buffers, DB/Redis backups) are ciphertext.
  • Only the delivery/replay/batch worker decrypts, transiently, in memory, at send time.

It does not claim:

  • That Pushrail "can never access" the data. The worker decrypts to deliver; an operator with the worker's private key and DB access could combine them. The guarantee is structural about the product surface, not absolute.
  • That ingestion never sees plaintext. The ingestion API receives the event in order to validate and route it, then seals it before persisting. The seal is about storage and retrieval, not the transient request.
  • Compliance certification. The feature supports a tenant's HIPAA/PCI obligations; it is not itself a certification or a BAA.

How to enable it per environment

Private Payload Mode is set per environment (production or staging) because an event's payload is one stored row shared across all of that event's destinations.

An Owner or Admin enables it from the environment settings control, which calls:

PATCH /settings/environments/:environment
{ "privatePayloadMode": true }

The response reports privatePayloadMode and the privatePayloadEnabledAt timestamp. Toggling it off sends { "privatePayloadMode": false }.

It is seal-forward only. Turning it on seals events ingested after the toggle. Turning it off only stops sealing new events; events that were already sealed stay sealed, because their plaintext was never kept. Existing sealed data ages out through your normal retention window.

Consequences

Sealing changes what the product surface can show in that environment.

  • Everyone sees [sealed], including you and your customer. There is no reveal path. The dashboard renders an explicit sealed state for event payloads and for delivery-attempt request/response bodies. Delivery metadata (status code, latency, error category, retry history, timestamps) stays fully visible, so you can still tell what happened and why.
  • Field suggestions, the sample-event sampler, and field-extraction preview are unavailable for sealed events. These features derive their value from reading real stored payloads, which are unreadable in the control plane. They report "unavailable in Private Payload Mode." You can still author transforms, filters, and scripts by supplying your own sample payloads.
  • The profile catalog does not populate from sealed events for the same reason.
  • Preview and dry-run inspect still work on payloads you supply yourself; the control plane never decrypts there, because you typed the data. Any delivery-attempt record that a dry-run persists in a sealed environment is sealed on write.
  • Replay still works. The worker holds the key, so replaying a sealed failure decrypts and re-delivers correctly, and creates no plaintext copy. One limitation: a backfill replay delivers sealed events only to destinations matched by event type; destinations whose routing depends on a field filter are skipped for sealed events, because the control plane cannot read the field to evaluate the filter.
  • Exports emit [sealed] for payload and free-text error columns in sealed environments; metadata columns export normally.

Crypto and operational model

Each sealed record uses a per-event AES-256-GCM data key (DEK) to encrypt the JSON contents. The DEK itself is wrapped with a platform RSA public key (OAEP-SHA256). Only a holder of the matching RSA private key can unwrap the DEK and decrypt the contents.

  • The public key lives in both the API and worker configuration, so ingestion can seal. The API holds the public key only, so it can seal but can never open.
  • The private key lives in the worker's secret store only. This is the structural guarantee: the control plane process never receives the private key, so any attempt to decrypt there fails by construction.
  • Every sealed record carries a sealKeyId, which leaves room for future key rotation and per-tenant keys without a breaking change.
  • Operational warning: losing the private key makes all sealed data unrecoverable. It must be backed up securely out of band.

Out of scope (v1)

  • Files and attachments are not sealed. File content is an external object-storage reference and never transits Pushrail's database. The sourceKey and attachment metadata remain visible in v1.
  • No backfill of pre-existing plaintext. Sealing is seal-forward only; older plaintext ages out via retention.
  • No per-tenant keys or key rotation yet. v1 uses a single platform keypair; sealKeyId makes both a later, non-breaking upgrade.
  • Redis sealing is limited to the buffered payload itself, not the surrounding queue infrastructure.