diff --git a/world-app/backup/components.mdx b/world-app/backup/components.mdx
index 3d5ecf9..a022c1f 100644
--- a/world-app/backup/components.mdx
+++ b/world-app/backup/components.mdx
@@ -45,6 +45,40 @@ Three factor types are recognized:
The challenge token is bound to the operation. A `Sync` challenge cannot be replayed against `/v1/delete-factor` because the embedded `ChallengeContext` differs. Challenges are single-use (Redis enforces).
+## Request attestation
+
+The three most sensitive endpoints are additionally gated by an **attestation gateway token** that binds a device-integrity attestation (Apple App Attest / Google Play Integrity) to the *specific request* being made:
+
+- `POST /v1/retrieve/from-challenge`
+- `POST /v1/verify-factor`
+- `POST /v1/delete-factor`
+
+The client obtains a JWT from the Attestation Gateway and sends it in the `attestation-token` header. The token's `jti` claim is a **request hash**; the Backup Service independently recomputes that hash from the request it receives and rejects the request when it does not match. The validator lives in [`backup-service/src/attestation_gateway.rs`](https://github.com/worldcoin/backup-service/blob/main/src/attestation_gateway.rs).
+
+### Request hash
+
+The request hash is the hex-encoded `SHA-256` of a canonical JSON object built from three fields (nested object keys recursively sorted alphabetically):
+
+| Field | Value |
+| --- | --- |
+| `body` | the request body, re-parsed and key-sorted (omitted for empty bodies) |
+| `method` | the uppercase HTTP method, e.g. `"POST"` |
+| `pathUri` | the **full** request path (see warning below) |
+
+Client and server must produce byte-identical canonical JSON. The shared implementation is Oxide's `AttestRequestHasher::generate_json_request_hash`, used by World App iOS, World App Android, and the Backup Service.
+
+
+`pathUri` is the **full** request path, **including the `/v1` API-version prefix** — e.g. `/v1/retrieve/from-challenge`, never `/retrieve/from-challenge`. Because the routes are mounted under an axum `.nest("/v1", …)`, the server must hash the *original, pre-routing* path (via `OriginalUri`), not the post-nest path, or the hash will not match the clients.
+
+
+
+Absent fields are **omitted** from the body, never serialized as `null`. Client and server treat an absent field and an explicit `null` differently when hashing, so a `null` causes a mismatch.
+
+
+### Enforcement
+
+Enforcement is controlled by a server-side flag. While **disabled**, the service still validates the token but, on failure, lets the request through and returns the reason in an `attestation-failure` response header — so clients and dashboards can detect drift before enforcement is enabled. While **enabled**, a failed validation rejects the request.
+
## What the service rejects
A handful of named errors are surfaced in client code paths and worth knowing by name: