Docs/Concepts/Gateway routing

Gateway routing

In short

TransportPolicy (OctetConfig.advanced.transport, new in 3.0) chooses where the SDK's network calls go: straight to Octet, through an HTTPS host you run, or through Octet's own gateway at gw.octetproof.com.

By default the SDK talks to the Octet backend at api.octetproof.com and, for a few enrichment calls, to a third-party host: IP location on iOS, and GNSS broadcast-ephemeris servers on Android. If your app's network surface is audited, for example by a firewall allowlist, an app-store network disclosure or a rule that the app talks only to your own domain, you can route every SDK request through one host.

The three modes

Mode Swift Kotlin Where calls go
Direct (default) .direct Mode.DIRECT Octet and the third-party hosts, as in 2.0.
Integrator gateway .integratorGateway Mode.INTEGRATOR_GATEWAY Every first-party call, and the third-party lookups, go to the HTTPS host you set in gateway.
Octet gateway .octetGateway Mode.OCTET_GATEWAY Every call goes to gw.octetproof.com.

In both gateway modes, the SDK sends every request to {gateway}{path}. Activation, verification, and proof and flag signing still terminate at Octet. The gateway only forwards requests, so the trust model is unchanged and only the network path moves.

// Your own gateway
let config = OctetConfig(
    licenseKey: "",
    advanced: AdvancedConfig(
        transport: TransportPolicy(
            mode: .integratorGateway,
            gateway: "https://api.example.com/octet")))

// Octet's gateway
let hosted = OctetConfig(
    licenseKey: "",
    advanced: AdvancedConfig(transport: .octetGateway))
// Your own gateway
val config = OctetConfig(
    licenseKey = "",
    advanced = AdvancedConfig(
        transport = TransportPolicy(
            mode = TransportPolicy.Mode.INTEGRATOR_GATEWAY,
            gateway = "https://api.example.com/octet")))

// Octet's gateway
val hosted = OctetConfig(
    licenseKey = "",
    advanced = AdvancedConfig(transport = TransportPolicy.octetGateway))

Octet gateway

The Octet gateway gives the app a single backend domain without a proxy for you to run. The SDK sends every call, first-party and third-party, to gw.octetproof.com. The host is built in, so there is no gateway to supply and any value you set is ignored. This mode uses standard certificate-authority validation, and pins is ignored. fallbackToDirect and the notes on country and state lookups apply as in the integrator-gateway mode. On iOS, the Octet gateway also serves the IP-based country hint.

TransportPolicy options

Option Default Effect
mode direct One of the three modes above.
gateway nil / null Base URL of your gateway. Used only in the integrator-gateway mode.
thirdParty .proxy / ThirdParty.PROXY Sends third-party lookups through the gateway. .disable / ThirdParty.DISABLE skips them in any mode, and a proof is still produced.
pins empty SPKI pins for the app-to-gateway TLS connection. Ignored in the Octet-gateway mode.
fallbackToDirect false After repeated gateway failures, critical calls go direct until the gateway recovers.
fallbackAfterFailures 3 How many transport failures in a row trip the fallback.
osAttestationInGatewayModes true false skips per-proof OS attestation in gateway modes, so proofs are marked un-attested.
borderDataUpdates true false keeps the bundled border map and never checks for a newer one.

What your gateway must forward

This table covers the integrator-gateway mode.

The SDK sends (under your gateway) Forward it to Notes
/v1/activate, /v1/heartbeat, /v1/deactivate https://api.octetproof.com/v1/… Signed bodies. Never modify.
/v1/flags, /v1/metrics, /v1/credits/… https://api.octetproof.com/v1/… Flag bundles are signed. Never modify.
/v1/proofs, /v1/proofs/auth, /v1/proofs/challenge https://api.octetproof.com/v1/… Proof body up to 256 KiB. Never modify.
/ext/ipgeo/… Optional, see IP-based country hint iOS SDK only. IP location hint.
/ext/gnss/… https://cddis.nasa.gov/archive/gnss/data/daily/… (fallback https://igs.ign.fr/pub/igs/data/…) Android SDK only. GNSS ephemeris. Large files, and the upstream may need NASA Earthdata credentials.
/ext/geo/… https://api.octetproof.com/ext/geo/… Both SDKs. Country and state lookups (/ext/geo/reverse) and border-map updates (/ext/geo/borders/). First-party and bearer-authenticated, so forward it like a /v1/… route: keep Authorization and send Host: api.octetproof.com.

Forward /ext/gnss/ only if you ship the Android SDK. /ext/geo/ is first-party and applies to both platforms.

Forwarding rules

  • Preserve the request exactly. Keep the method, the path after your prefix, the query string, and all headers, especially Authorization, Content-Type, and any X-Octet-… header.
  • Never modify the body of /v1/proofs, /v1/flags, or /v1/activate. Each signature covers the exact bytes, so any rewrite, including re-serializing the JSON, breaks verification.
  • Use TLS 1.2 or higher to the upstream, verify the upstream certificate, and send Host: api.octetproof.com with matching SNI on the first-party routes.
  • Pass status codes and the Retry-After header through unchanged. The SDK honors 429.
  • Allow request bodies of at least 256 KiB, and give the upstream a timeout at least as long as the SDK's. Activation and attestation can be slow.

Reference configurations

Replace api.example.com/octet with your own gateway base URL, and keep only the ext blocks for the platforms you ship.

nginx

# All first-party routes are under /v1/: activation, flags, metrics, credits,
# proofs (incl. /v1/proofs/auth + /v1/proofs/challenge).
location /octet/v1/ {
    proxy_pass                  https://api.octetproof.com/v1/;
    proxy_ssl_server_name       on;
    proxy_set_header Host       api.octetproof.com;
    proxy_pass_request_headers  on;
    client_max_body_size        256k;
}
# Android apps only:
location /octet/ext/gnss/  { proxy_pass https://cddis.nasa.gov/archive/gnss/data/daily/; proxy_ssl_server_name on; }
# All apps: country and state lookups, border-map updates (first-party, bearer-authed):
location /octet/ext/geo/   { proxy_pass https://api.octetproof.com/ext/geo/; proxy_ssl_server_name on; proxy_set_header Host api.octetproof.com; }

Cloudflare Worker

const UPSTREAM = {
  "/v1/":        "https://api.octetproof.com/v1/",                   // all first-party (incl. /v1/proofs/auth, /v1/proofs/challenge)
  "/ext/gnss/":  "https://cddis.nasa.gov/archive/gnss/data/daily/",  // Android apps
  "/ext/geo/":   "https://api.octetproof.com/ext/geo/",              // all apps: country and state lookups (first-party)
};

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const path = url.pathname.replace(/^\/octet/, "");   // strip your gateway prefix
    const match = Object.entries(UPSTREAM).find(([prefix]) => path.startsWith(prefix));
    if (!match) return new Response("not found", { status: 404 });
    const [prefix, base] = match;
    const target = base + path.slice(prefix.length) + url.search;
    // Forward method, headers, and body untouched; return the upstream response as-is.
    return fetch(target, {
      method: request.method,
      headers: request.headers,
      body: request.body,
      redirect: "follow",
    });
  },
};

AWS

  • API Gateway (HTTP API). Add one HTTP-proxy integration per prefix. For example, route ANY /octet/v1/{proxy+} to https://api.octetproof.com/v1/{proxy}, and add one each for /octet/ext/geo/ and, for Android, /octet/ext/gnss/. Leave request-parameter mapping untouched so headers and the query string pass through unchanged.
  • CloudFront. Define one origin per upstream and one cache behavior per path pattern (/octet/v1/*, /octet/ext/geo/*, /octet/ext/gnss/*). Attach an origin-request policy that forwards all headers, query strings, and cookies, and the managed CachingDisabled cache policy so nothing is buffered or rewritten. Set each origin to HTTPS-only.

Things to know

Certificate pinning

The SDK's built-in pins are for Octet's certificates, so they do not apply once traffic goes through your gateway: the app sees your certificate. To keep pinning on the app-to-gateway connection, pass your gateway's SPKI pins in pins:

transport: TransportPolicy(
    mode: .integratorGateway,
    gateway: "https://api.example.com/octet",
    pins: ["<base64-sha256-of-your-proxy-SPKI>", "<backup-pin>"])
transport = TransportPolicy(
    mode = TransportPolicy.Mode.INTEGRATOR_GATEWAY,
    gateway = "https://api.example.com/octet",
    pins = listOf("<base64-sha256-of-your-proxy-SPKI>", "<backup-pin>"))

Compute a pin with:

openssl x509 -in cert.pem -pubkey -noout | openssl pkey -pubin -outform DER \
  | openssl dgst -sha256 -binary | openssl enc -base64

Pin a stable point in the chain, such as a CA intermediate or root. A managed certificate (Cloudflare, ACME, Let's Encrypt) gets a new key on renewal, which breaks a pin on the leaf certificate the next time it rotates. Always include a backup pin. Leave pins empty, the default, to rely on standard certificate-authority validation.

Fallback to direct

fallbackToDirect is off by default, so a gateway outage makes the SDK's calls fail. Set it to true to keep the SDK working through an outage. After fallbackAfterFailures transport-level failures in a row (connect, TLS, DNS or timeout, and never an HTTP status), the SDK sends the critical calls (activation, proof upload, and usage reporting) directly to Octet. It then checks your gateway again and switches back once the gateway recovers. While the fallback is active, those calls go to api.octetproof.com directly, so the app no longer talks to a single domain. A deployment that must keep to one domain should leave it off. Telemetry and flags always stay on the gateway.

transport: TransportPolicy(
    mode: .integratorGateway,
    gateway: "https://api.example.com/octet",
    fallbackToDirect: true,
    fallbackAfterFailures: 3)
transport = TransportPolicy(
    mode = TransportPolicy.Mode.INTEGRATOR_GATEWAY,
    gateway = "https://api.example.com/octet",
    fallbackToDirect = true,
    fallbackAfterFailures = 3)

OS attestation in gateway modes

In a gateway mode, the SDK still runs per-proof OS attestation by default: App Attest on iOS, which calls Apple, and Play Integrity on Android, which calls Google. These OS calls cannot go through a proxy. Set osAttestationInGatewayModes to false to skip them, so the app makes no attestation calls to Apple or Google. Those proofs are signed by the device key but are not hardware-attested, and a verifier reports them as attested: false. A verifier that requires attestation fails them. Base your own acceptance on the proof's attested flag. Startup activation still attests, so the app still activates.

transport: TransportPolicy(
    mode: .octetGateway,
    osAttestationInGatewayModes: false)   // un-attested proofs
transport = TransportPolicy(
    mode = TransportPolicy.Mode.OCTET_GATEWAY,
    osAttestationInGatewayModes = false)   // un-attested proofs

Border-map updates

The SDK claims a country or state only when the device is clearly inside it, measured against a map of land borders that ships with the SDK. At most once a day, in the background and never while making a proof, the SDK checks whether a newer map has been published, and uses it only if its signature verifies. The request goes to the host the SDK already talks to (your gateway in a gateway mode, Octet's activation host in direct mode) under /ext/geo/borders/, and carries nothing about the device or its location. Set borderDataUpdates to false to keep the bundled map and never make the request.

transport: TransportPolicy(mode: .octetGateway, borderDataUpdates: false)
transport = TransportPolicy(mode = TransportPolicy.Mode.OCTET_GATEWAY, borderDataUpdates = false)

Country and state lookups

In a gateway mode, the SDK turns a coordinate into a country or state at {gateway}/ext/geo/reverse instead of calling the operating system's geocoder. Forward that route, or country proofs return INDETERMINATE unless fallbackToDirect is on.

IP-based country hint

This applies to the iOS SDK. With your own gateway, Octet provides no upstream for /ext/ipgeo/. Leave that route unrouted, or set TransportPolicy.thirdParty to .disable so the SDK skips the lookup. The hint is optional, and a proof is still produced either way. The Octet gateway serves the hint itself.

In direct mode, the iOS SDK uses ipwho.is for IP location. Add ipwho.is to any network allowlist, or set thirdParty to .disable.

Where to go next