Relay integration
in three steps.
Everything you need to route Oblivious HTTP traffic through ohttp.io, from key discovery to production hardening.
Quickstart
The OHTTP promise in one sentence: a relay knows who you are but never what you're doing; a gateway knows what you're doing but never who you are. The guarantee holds because the two roles are run by different companies, and an OHTTP exchange has three moving parts: your clients, the ohttp.io relay, and an Oblivious Gateway Resource (yours, or a partner's). The client never talks to the gateway directly, that's the whole point.
1. Create a relay endpoint
Sign up and register the gateway URI you want to relay to. You'll receive a dedicated relay URL:
https://relay.ohttp.io/<your-relay-id>
2. Publish the gateway key configuration
Clients need the gateway's HPKE key configuration before they can
encapsulate anything. Gateways serve it as application/ohttp-keys;
clients fetch it out of band or via the well-known URI.
curl -H "Accept: application/ohttp-keys" \
https://gateway.example.com/.well-known/ohttp-gateway
3. Encapsulate and POST
The client encodes its request as Binary HTTP (RFC 9292), encrypts it with HPKE (RFC 9180) to the gateway's public key, and POSTs the result to the relay. The relay forwards it and returns the encapsulated response.
curl -X POST https://relay.ohttp.io/<your-relay-id> \ -H "Content-Type: message/ohttp-req" \ --data-binary @encapsulated-request.bin \ -o encapsulated-response.bin
Relay endpoints
The relay surface is deliberately tiny, one resource, one method.
| Property | Value |
|---|---|
| Method | POST only. Anything else receives 405. |
| Request type | Content-Type: message/ohttp-req - an HPKE-encapsulated Binary HTTP request. |
| Response type | Content-Type: message/ohttp-res - the gateway's encapsulated response, streamed back unmodified. |
| Protocols | HTTPS only (RFC 9458 requires HTTPS on both hops). HTTP/1.1 and HTTP/2 on all plans; HTTP/3 (QUIC) from the Scaling tier up. |
| Errors | 400 malformed or empty content · 413 oversized · 429 rate limited · 502/504 gateway unreachable or timed out. |
| Rate limits | Community 2 req/s (no bursting) · Starter 25 req/s · Scaling 150 req/s · Pro 1,000 req/s. Paid plans can burst to 2× per region for short spikes. Enterprise rates are custom. |
| Max request size | Community 1 MB · Starter 50 MB · Scaling 100 MB · Pro unlimited (fair use). Enterprise limits are custom. Bodies above the cap are rejected with 413. |
| Bandwidth / month | Community 50 GB · Starter 150 GB · Scaling 500 GB · Pro 4 TB. Enterprise custom. Overage billed at $0.05/GB. |
Relays may reject obviously invalid requests (RFC 9458 §5), but a relay can never generate an encapsulated response itself, it has no keys. Error responses from the relay are always plain HTTP and never reveal anything about encapsulated content.
Key configuration
A gateway's key configuration tells clients how to encrypt. It is
serialized as application/ohttp-keys and contains:
| Field | Size | Purpose |
|---|---|---|
| Key ID | 8 bits | Identifies which gateway key the client selected. |
| KEM ID | 16 bits | Key encapsulation mechanism, e.g. DHKEM(X25519, HKDF-SHA256). |
| Public key | variable | The gateway's HPKE public key. |
| Symmetric algorithms | variable | Supported KDF/AEAD pairs, e.g. HKDF-SHA256 + AES-128-GCM. |
Clients must treat key configurations as integrity-protected and attributable to the gateway (RFC 9458 §6.1). Gateways should rotate keys regularly, OHTTP has no forward secrecy over the lifetime of a key configuration, so rotation bounds the exposure window.
Chunked OHTTP
Classic OHTTP buffers the entire request and response before encryption - fine for telemetry, painful for anything that streams. Chunked Oblivious HTTP (draft-ietf-ohai-chunked-ohttp) encrypts messages incrementally, so responses can flow as they're produced. ohttp.io accepts the chunked media types from the Starter tier up.
| Media type | Direction |
|---|---|
| message/ohttp-chunked-req | Client → relay → gateway, streamed request chunks. |
| message/ohttp-chunked-res | Gateway → relay → client, streamed response chunks. |
This is the right transport for:
- Anonymous LLM inference - tokens stream to the user as they're generated, with no identity attached to the prompt.
- Agent tool calls - autonomous agents can invoke external tools without revealing which principal they act for.
- Large or long-lived responses - map tiles, dataset slices, media range requests.
Privacy Pass
Rate limiting an anonymous service is a paradox: the usual tool is IP tracking, which is exactly what OHTTP exists to eliminate. Privacy Pass (RFC 9576–9578) resolves it with anonymous tokens, clients prove they're rate-limited participants without revealing who they are.
ohttp.io operates the issuance and redemption for you:
- Issuance - clients obtain unlinkable tokens through an attestation flow you configure (device attestation, account-backed, or anonymous credits).
- Redemption - tokens accompany encapsulated requests; the relay enforces per-token budgets without IPs, accounts, or CAPTCHAs.
- Anti-abuse without surveillance - abuse is bounded by token economics, not by watching users.
Privacy Pass support is included from the Scaling tier up; full issuance, where ohttp.io operates the issuer for you, is part of the Pro tier. The standards involved: RFC 9576 (architecture), RFC 9577 (HTTP authentication scheme), RFC 9578 (issuance protocols).
Client libraries
ohttp.io interoperates with any RFC 9458-conformant implementation. Popular open-source options:
- Rust - the
ohttpcrate, used in production by privacy infrastructure like Divvi Up. - Go -
ohttp-go, plus relays likepogif you self-host for testing. - JavaScript / TypeScript -
ohttp-jsfor browser and Node clients. - C++ -
ohttp-gpfor Chromium-derived networking stacks.
Configure the client with two URIs, the gateway's key config and your ohttp.io relay:
let ohttp_config = OhttpConfig { key_configs: Url::parse("https://gateway.example.com/ohttp-keys")?, relay: Url::parse("https://relay.ohttp.io/<your-relay-id>")?, };
Gateway-in-a-box
RFC 9458 requires the relay and gateway to be run by different entities - so we can't run your gateway for you. What we can do is make running one yourself an afternoon's work instead of a quarter's:
- Cloudflare Worker gateway template - a deployable Oblivious Gateway Resource with HPKE decapsulation, key configuration serving, and chunked support. Fork, point at your backend, deploy.
- Terraform module - provisions the gateway, its key rotation schedule, and the DNS records (including RFC 9540 service-binding) in one apply.
- Key-rotation runbooks - tested procedures for rotating HPKE key configurations without breaking in-flight clients, including overlap windows and rollback.
- Auto provider compliance - on Scaling and Pro we continuously verify that your configured gateway resolves and routes on a network independent from ours. If a gateway is moved onto infrastructure we also run on (for example the same cloud or edge provider), we alert you, or refuse the mapping, because relay and gateway on one network silently erodes unlinkability.
- Key-config consistency checker - verifies every client sees the same key configuration, as RFC 9458 §6.1 requires, across regions and over time.
Your gateway stays in your account, under your keys, operated by your team. The independence the protocol depends on is preserved, we just remove the excuse for not deploying it.
Relay guarantees
RFC 9458 §6.2 defines what a conformant relay must and must not do. These are our operating commitments, not aspirations:
- No plaintext access. The relay handles HPKE ciphertext only. It cannot read methods, paths, headers, or bodies inside the encapsulation.
- Zero logging. We retain nothing beyond billing records. Client IP addresses, TLS connection metadata, timing, and ciphertext bodies are discarded the moment a request completes.
- No metadata injection. We never add
Forwarded,Via, or any client-identifying fields when forwarding to the gateway. - Unknown fields dropped. Header fields the client adds beyond the required content type are removed before forwarding, per §6.2.
- No collusion. ohttp.io is legally unconnected to every other relay and gateway operator, no shared ownership, partnerships, or data agreements.
- One-to-one mapping. Each relay endpoint forwards to exactly one configured gateway, as the protocol requires.
- GDPR, by architecture. We never see request payloads and retain nothing beyond billing records, so most processing obligations fall to you as controller. What little we do process is set out in our privacy policy.
What transiently passes through memory: your client's IP address, TLS connection metadata, timing, and ciphertext sizes, the half of the picture the gateway never sees. Unlinkability holds because relay and gateway are operated independently, and ohttp.io enforces that independence two ways: we are legally unconnected to other operators, and no customer may use us for both ends of a flow.
DNS discovery
Services can advertise OHTTP support with an HTTPS DNS resource record
(RFC 9540). The ohttp SvcParamKey signals that a service is
reachable via Oblivious HTTP:
service.example.com. 7200 IN HTTPS 1 . ( alpn=h2 ohttp )
Clients resolving this record learn to fetch the key configuration from
/.well-known/ohttp-gateway on the same origin, then route
encapsulated requests through a pre-configured relay, yours at ohttp.io.
Migration guide
Moving to ohttp.io from another hosted relay, or from a self-hosted one - is a configuration change, not a code change. OHTTP is a standard; clients don't care which relay forwards their ciphertext.
From another hosted relay
- Create your ohttp.io relay endpoint pointing at your existing gateway URI.
- Update the relay URL in your client configuration (one string in most libraries).
- Run both relays in parallel during your deploy window, the gateway sees no difference.
- Decommission the old relay once client rollout completes.
From a self-hosted relay
- Same steps as above, plus you get to delete a service, an on-call rotation, and a TLS certificate from your life.
Frequently asked questions
Can ohttp.io read the requests it relays?
No. Requests are HPKE-encrypted to the gateway's keys before they reach us. We see ciphertext, client IP addresses, and timing, never methods, paths, headers, or bodies.
Why does the relay need to be independent from the gateway?
RFC 9458 §6 is explicit: if one entity runs both relay and gateway for the same traffic, it can combine client network identity with request content and the unlinkability property collapses. ohttp.io enforces this commercially, a customer may use us for one side of a flow, never both, and we are legally unconnected to every other relay and gateway operator.
What does ohttp.io log?
Nothing beyond billing records. Client IP addresses, connection metadata, timing, and ciphertext bodies exist only in memory for the duration of a request and are discarded immediately after. Aggregate byte and request counts for billing are the only records we keep.
Does OHTTP provide forward secrecy?
Not over the lifetime of a key configuration. If a gateway's private key is compromised, recorded ciphertext under that key can be recovered. Regular key rotation limits the window; TLS protects each hop in transit.
What is OHTTP good for?
Stateless, moderately sensitive requests: telemetry and crash reporting, DNS queries (Oblivious DoH), safe-browsing lookups, anonymous surveys, map tile fetches, anywhere linking a request to an identity would teach the server something about a user.
What does OHTTP not protect?
Content the client itself puts in the request. If the body carries a user ID or auth token, the gateway can correlate regardless of transport privacy. OHTTP hides who sent the request, not what was sent.
When should I use chunked OHTTP instead of classic?
Whenever the response benefits from streaming, LLM inference, agent tool calls, large payloads, or when request/response sizes are unknown upfront. Classic single-shot OHTTP remains ideal for small, stateless requests like telemetry pings and DNS queries.
How do you rate-limit without tracking IPs?
With Privacy Pass tokens (RFC 9576–9578). Clients present unlinkable anonymous tokens with their requests; we enforce per-token budgets. Abuse is bounded without IP logs, accounts, or CAPTCHAs.
Spin up your relay endpoint.
Free tier included. Point your first client at ohttp.io in minutes.